Я предполагаю, что вы знакомы или, по крайней мере, знаете функцию водопада в асинхронном модуле. Не стесняйтесь читать мою предыдущую статью, если вы понятия не имеете, что это такое. На этот раз я собираюсь создать свою собственную версию асинхронного водопада с изюминкой. В чем поворот? помните, как выглядит типичная функция водопада?

Вместо использования обратного вызова моя версия водопада вернет обещание. Ага, обещай. Таким образом, типичный вызов водопадной функции в моей версии будет выглядеть так.

Спецификация

Поскольку это попытка воссоздать асинхронный водопад, наша функция должна уметь делать все, что может асинхронный водопад.

  1. Он должен принимать функцию массива и выполнять ее последовательно в зависимости от порядка элементов в массиве.
  2. Он должен иметь возможность отловить ошибку, вызванную любой функцией в массиве.
  3. Он должен поймать результат последней функции
  4. Он должен возвращать обещание вместо использования обратного вызова

Код

Имея в виду эти спецификации, мы можем приступить к созданию функции. Наш первый грубый перевод этой спецификации в код будет примерно таким.

Как видите, с этой первой итерацией мы уже (отчасти) выполнили спецификации 3 и 4 (и частично 1). Что такое finalCB? По сути, идея с finalCB заключается в том, что мы добавим «последнюю» функцию в funcs. Эта функция разрешает или отклоняет обещание в зависимости от того, как последняя функция вызывает этот finalCB.

Далее нам нужно обернуть функции, переданные в водопад, другой функцией, которая действует как finalCB. Это позволит нам выполнять функции в последовательном порядке и обрабатывать ошибки, если одна из функций возвращает ошибку, следовательно, выполняется спецификация 1 и 2. Давайте назовем эту функцию-оболочку wrapFun и фактическую функцию actFun. wrapFun будет действовать как finalCB, он в основном принимает любое количество аргументов, но первый особенный. Он будет рассматривать первый аргумент как ошибку, если он не пустой, он отклонит обещание и отклонит этот первый аргумент, а если он пуст, вместо разрешения обещания, такого как CB, он вызовет actFun с остальными аргументами, переданными в wrapFun. Давайте посмотрим, как я реализую этот wrapFun.

В строке 15 происходит интересная вещь: мы передаем next wrapFun в args, и если next wrapFun не определено, мы вместо этого запушиваем finalCB (помните, что мы внедрим finalCB как «последнюю» функцию?). Подождите минутку, а как мы протолкнем следующий wrapFun, если у нас на самом деле нет следующего wrapFun? ну, wrapFunc[idx+1] будет оцениваться после того, как все элементы wrapFuncs будут инициированы, поэтому во время выполнения для нас будет wrappedFuncs[idx+1] :). Закончив с аргументами, мы вызываем actFun с этими новыми аргументами. Наконец, мы начинаем цепочку выполнения, вызывая первую функцию wrapFun (см. строку 20 на рис. 4).

Исполнение

Настал момент истины, попробуйте выполнить этот фрагмент кода.

Вы должны получить что-то вроде этого

Hello
World

Что делать, если myFirstFunction возвращает ошибку?

function myFirstFunction(cb) {
return cb(new Error('Error Test'), 'Hello');
}

Наш код вернет это

Error: Error Test
    at Function.myFirstFunction (/home/x/concurrency-lab/async-waterfall.js:28:13)
    at Array.<anonymous> (/home/x/concurrency-lab/async-waterfall.js:14:14)
    at Promise (/home/x/concurrency-lab/async-waterfall.js:18:20)
    at Promise (<anonymous>)
    at waterfall (/home/x/concurrency-lab/async-waterfall.js:4:10)
    at Object.<anonymous> (/home/x/concurrency-lab/async-waterfall.js:23:1)
    at Module._compile (module.js:569:30)
    at Object.Module._extensions..js (module.js:580:10)
    at Module.load (module.js:503:32)
    at tryModuleLoad (module.js:466:12)

Вывод

Асинхронный водопад можно воссоздать с обещанием (правда, это звучит неправильно? :))

Я кодирую эту функцию водопада просто для развлечения, вы никогда не должны использовать ее в продакшене.