HTML Diff
0 added 0 removed
Original 2026-01-01
Modified 2026-02-26
1 <p><strong>В этом руководстве наглядно объясняем идею промисов на примере работы waterfall. Материал будет полезен тем, кто уже знаком с колбеками в асинхронных вызовах, а также тем, кто только приступает к изучению промисов.</strong></p>
1 <p><strong>В этом руководстве наглядно объясняем идею промисов на примере работы waterfall. Материал будет полезен тем, кто уже знаком с колбеками в асинхронных вызовах, а также тем, кто только приступает к изучению промисов.</strong></p>
2 <p>Прежде, чем перейти к подробному описанию промисов, разберемся, как они работают. Их суть заключается в том, что когда мы пишем обычный (не асинхронный) код, мы просто пишем команды друг за другом - в таком же порядке, как они вызываются</p>
2 <p>Прежде, чем перейти к подробному описанию промисов, разберемся, как они работают. Их суть заключается в том, что когда мы пишем обычный (не асинхронный) код, мы просто пишем команды друг за другом - в таком же порядке, как они вызываются</p>
3 <p>Предположим, что у нас есть три функции. getUsers() возвращает список пользователей, getTasks() - список задач, а tasksDist() распределяет все задачи между пользователями, возвращая в качестве результата тот же список задач, но уже с указанными исполнителями.</p>
3 <p>Предположим, что у нас есть три функции. getUsers() возвращает список пользователей, getTasks() - список задач, а tasksDist() распределяет все задачи между пользователями, возвращая в качестве результата тот же список задач, но уже с указанными исполнителями.</p>
4 <p>В обычном (не асинхронном) коде можно вызывать функции на каждой строке и сразу получить результат:</p>
4 <p>В обычном (не асинхронном) коде можно вызывать функции на каждой строке и сразу получить результат:</p>
5 <p>Если мысленно представить порядок работы такого кода, то можно выстроить такую структуру:</p>
5 <p>Если мысленно представить порядок работы такого кода, то можно выстроить такую структуру:</p>
6 <p>Теперь предположим, что функции getUsers(), getTasks() и tasksDist() асинхронные, при этом никакие изменения в код не вносятся. В этом случае порядок их выполнения меняется: каждый вызов асинхронной функции переносит выполнение кода в этой функции в "отдельную временную ветку". В этом основная сложность понимания для новичков: код начинает работать совершенно не так, как раньше. Мы как Нео, проснувшийся из матрицы, обнаруживаем совершенно другой мир, скрывавшийся в привычных нам вещах. Давайте представим, как изменения будут выглядеть на нашей схеме:</p>
6 <p>Теперь предположим, что функции getUsers(), getTasks() и tasksDist() асинхронные, при этом никакие изменения в код не вносятся. В этом случае порядок их выполнения меняется: каждый вызов асинхронной функции переносит выполнение кода в этой функции в "отдельную временную ветку". В этом основная сложность понимания для новичков: код начинает работать совершенно не так, как раньше. Мы как Нео, проснувшийся из матрицы, обнаруживаем совершенно другой мир, скрывавшийся в привычных нам вещах. Давайте представим, как изменения будут выглядеть на нашей схеме:</p>
7 <p>Обратите внимание, что каждый вызов асинхронной функции выполняется в своем скоупе. И каждый такой вызов может работать сколько угодно времени: нельзя заранее сказать, что при таком вызове, например, функция getUsers() выполнится и вернет результат до того, как начнет выполнение tasksDist():</p>
7 <p>Обратите внимание, что каждый вызов асинхронной функции выполняется в своем скоупе. И каждый такой вызов может работать сколько угодно времени: нельзя заранее сказать, что при таком вызове, например, функция getUsers() выполнится и вернет результат до того, как начнет выполнение tasksDist():</p>
8 <p>Для удобства я выделил выполнение каждой функции цветом и добавил направляющую времени.</p>
8 <p>Для удобства я выделил выполнение каждой функции цветом и добавил направляющую времени.</p>
9 <p>Несмотря на то, что функция getUsers() вызвалась самой первой, она выполняется последней. При этом ее результат нужен для выполнения tasksDist(). Хаос вносит и синхронный код, к которому мы так привыкли. В этом примере функции console.log() выполняются по порядку, но не дожидаясь выполнения асинхронных функций.</p>
9 <p>Несмотря на то, что функция getUsers() вызвалась самой первой, она выполняется последней. При этом ее результат нужен для выполнения tasksDist(). Хаос вносит и синхронный код, к которому мы так привыкли. В этом примере функции console.log() выполняются по порядку, но не дожидаясь выполнения асинхронных функций.</p>
10 <p>К графику выше стоит относиться критически. Во-первых, связи с многопоточностью тут нет, а рантайм выполняется в однопоточном режиме. Во-вторых, как следует из<a>этого урока</a>, код попадает в очередь на выполнение. Именно поэтому сначала выполняется синхронный код, и только в конце - асинхронный.</p>
10 <p>К графику выше стоит относиться критически. Во-первых, связи с многопоточностью тут нет, а рантайм выполняется в однопоточном режиме. Во-вторых, как следует из<a>этого урока</a>, код попадает в очередь на выполнение. Именно поэтому сначала выполняется синхронный код, и только в конце - асинхронный.</p>
11 <p>Такое представление ближе к реальности. Ещё немного - и мы хакнем эту матрицу и сможем использовать ее себе во благо. Для решения этой задачи нужно приостановить вызов одних элементов кода до завершения выполнения других. В нашем примере функция tasksDist() должна выполниться только после выполнения getUsers() и getTasks(), поскольку нам нужны данные, которые вернут эти функции.</p>
11 <p>Такое представление ближе к реальности. Ещё немного - и мы хакнем эту матрицу и сможем использовать ее себе во благо. Для решения этой задачи нужно приостановить вызов одних элементов кода до завершения выполнения других. В нашем примере функция tasksDist() должна выполниться только после выполнения getUsers() и getTasks(), поскольку нам нужны данные, которые вернут эти функции.</p>
12 <p>Хорошая новость: нам не нужно придумывать сложные решения с высчитыванием времени паузы или чем-то в этом роде. Самый простой способ - передать функцию tasksDist() в качестве колбека. Сложность только в том, что мы не знаем, какая из функций getUsers()/ getTasks() выполнится раньше, а какая - позже. Другими словами, непонятно, в какую из них нужно передать колбек. Это решаемый вопрос, но у колбэков есть и другой недостаток - они попросту неудобны. По этому поводу даже существует отдельное понятие -<a>Callback Hell</a>. Ради упрощения в нашем примере сделаем так, чтобы сначала выполнился getUsers(), после него getTasks(), а в конце - tasksDist():</p>
12 <p>Хорошая новость: нам не нужно придумывать сложные решения с высчитыванием времени паузы или чем-то в этом роде. Самый простой способ - передать функцию tasksDist() в качестве колбека. Сложность только в том, что мы не знаем, какая из функций getUsers()/ getTasks() выполнится раньше, а какая - позже. Другими словами, непонятно, в какую из них нужно передать колбек. Это решаемый вопрос, но у колбэков есть и другой недостаток - они попросту неудобны. По этому поводу даже существует отдельное понятие -<a>Callback Hell</a>. Ради упрощения в нашем примере сделаем так, чтобы сначала выполнился getUsers(), после него getTasks(), а в конце - tasksDist():</p>
13 <p>На схеме результат можно представить в таком виде:</p>
13 <p>На схеме результат можно представить в таком виде:</p>
14 <p>Этот вариант больше подходит для работы: все функции в коде выполняются последовательно друг за другом. Добавим в нашу функцию вызов колбека:</p>
14 <p>Этот вариант больше подходит для работы: все функции в коде выполняются последовательно друг за другом. Добавим в нашу функцию вызов колбека:</p>
15 <p>Существует более лаконичное решение. По сути, нужно сделать последовательную цепочку вызовов асинхронных функций. При этом от вложенных вызовов колбеков можно отказаться, поскольку функции выполняются последовательно: по принципу, близкому к<a>пайплайну</a>. Реализовать идею можно с помощью<a>функции waterfall из библиотеки async</a>.</p>
15 <p>Существует более лаконичное решение. По сути, нужно сделать последовательную цепочку вызовов асинхронных функций. При этом от вложенных вызовов колбеков можно отказаться, поскольку функции выполняются последовательно: по принципу, близкому к<a>пайплайну</a>. Реализовать идею можно с помощью<a>функции waterfall из библиотеки async</a>.</p>
16 <p>Добавим эту функцию в код:</p>
16 <p>Добавим эту функцию в код:</p>
17 <p>В качестве первого параметра принимается массив функций. Каждая из них, в свою очередь, принимает параметр callback. Например:</p>
17 <p>В качестве первого параметра принимается массив функций. Каждая из них, в свою очередь, принимает параметр callback. Например:</p>
18 <p>Этот колбек - следующая функция в списке:</p>
18 <p>Этот колбек - следующая функция в списке:</p>
19 <p>Обратите внимание, что эта функция уже принимает два параметра: users и callback - это скрытая работа waterfall. Кроме самого результата, который передается в предыдущей функции из массива callback(users), добавился новый (callback), который будет следующей функцией в списке:</p>
19 <p>Обратите внимание, что эта функция уже принимает два параметра: users и callback - это скрытая работа waterfall. Кроме самого результата, который передается в предыдущей функции из массива callback(users), добавился новый (callback), который будет следующей функцией в списке:</p>
20 <p>Заметьте, что во втором вызове я передал в колбек два значения: callback(users, tasks). Это сделано для того, чтобы в третьей функции не потерялись данные из первой. Альтерантивный вариант: создать глобальную переменную let и в ней сохранить данные.</p>
20 <p>Заметьте, что во втором вызове я передал в колбек два значения: callback(users, tasks). Это сделано для того, чтобы в третьей функции не потерялись данные из первой. Альтерантивный вариант: создать глобальную переменную let и в ней сохранить данные.</p>
21 <p>Последняя функция в массиве тоже принимает колбек, вместо которого подставится последняя функция. Ее waterfall должен отличать от остальных: в ней не должно быть callback. Поэтому эта функция передаётся не внутри массива, а отдельным параметром:</p>
21 <p>Последняя функция в массиве тоже принимает колбек, вместо которого подставится последняя функция. Ее waterfall должен отличать от остальных: в ней не должно быть callback. Поэтому эта функция передаётся не внутри массива, а отдельным параметром:</p>
22 <p>Наш код остался на колбеках, но waterfall "выпрямил" его. Благодаря ей нет необходимости делать множество вложенных вызовов с отступами: код выглядит как прямая цепочка вызовов. Эта идея напрямую нас подводит к тому, как работают промисы.</p>
22 <p>Наш код остался на колбеках, но waterfall "выпрямил" его. Благодаря ей нет необходимости делать множество вложенных вызовов с отступами: код выглядит как прямая цепочка вызовов. Эта идея напрямую нас подводит к тому, как работают промисы.</p>