Если вы уже говорите "Ого." функция, которая возвращает функцию, которая
возвращает обязательство, которое вызывает функцию, которая возвращает обя
зательство... У меня уже голова кружится!': я могу вас понять: добавление периода
в возвращающую обязательство функцию не является тривиальной задачей и требу
ет напряжения всех извилин. Полное понимание этой функции является упражнени-
ем для "продвинутого" читателя. Однако использовать эту функцию весьма просто:
мы можем добавить период к любой функции, которая возвращает обязательство.
Скажем, наша самая медленная ракета достигает орбиты через 1 О секунд (разве не
прекрасны ракетные технологии будущего?). Таким образом, мы устанавливаем пе
риод на 1 1 секунд.
с . go ( )
. then ( addTimeout ( launch, 4 * 1 0 00 ) )
. then ( function (msg) {
console . log (msg) ;
})
. ca t ch ( funct ion ( er r ) (
console . error ( "Xьюcтoн, у нас проблемы: " + err . me s sage ) ;
});
Теперь наша цепь обязательств всегда будет завершаться, даже когда функция
launch ведет себя плохо.
Генераторы
Как уже обсуждалось в главе 1 2, генераторы обеспечивают двухстороннюю связь
между функцией и ее вызывающей стороной. Генераторы синхронны по своей при
роде, но, будучи объединены с обязательствами, обеспечивают мощную технологию
для управления асинхронным кодом в JavaScript.
Давайте припомним главную сложность асинхронного кода: его труднее писать,
чем синхронный код. Когда мы решаем задачу, наш ум стремится свести ее к син
хронному виду: этап 1, этап 2, этап 3 и т.д. Однако при этом подходе могут быть про
блемы производительности, которых нет при асинхронном подходе. Разве не было
246 Глава 1 4. Асинхронное программирование
бы хорошо иметь преимущества производительности асинхронных технологий без
дополнительных концептуальных трудностей? Вот где могут пригодиться генерато
ры.
Рассмотрим использованный ранее пример "проклятья обратных вызовов": чте
ние трех файлов, задержка на одну минуту и последующая запись содержимого пер
вых трех файлов одного за другим в четвертый файл. Наш человеческий разум хо
тел бы написать это в виде примерно такого псевдокода.
dataA читаем содержимое файла ' a . txt '
dataB = читаем содержимое файла ' b . txt '
dataC = читаем содержимое файла ' c . txt '
Ждем 6 0 секунд
Записываем dataA + dataB + dataC в файл ' d . txt '
Генераторы позволяют нам писать код, который выглядит очень похоже на этот...
однако необходимые функциональные возможности не появятся как из коробки:
сначала придется проделать небольшую работу.
Первое, что необходимо, - это способ превратить функцию обратного вызова
с первым аргументом для передачи ошибки Node в обязательство. Мы инкапсулиру
ем это в функцию nfcall (Node function call - вызов функции Node).
function nfcall ( f , . . . args ) {
return new Promise ( funct ion ( re s o lve , rej ect ) {
f . cal l ( nu l l , . . . args , function ( err, . . . args )
i f ( err) return rej ect ( err) ;
resolve ( args . length<2 ? args [ O ] : args ) ;
});
}) ;
Эта функция названа в честь метода nfca l l из библиотеки обяза
тельств Q (и реализована на его основе). Если вам необходимы эти
функциональные возможности, лучше всего воспользуйтесь библио
текой Q. Она включает не только этот метод, но и много других по-
лезных методов, также связанных с обязательствами. Я же представ
ляю реализацию nfcall для демонстрации того, что никакого "вол
шебства" здесь нет.
Теперь мы можем преобразовать любой метод, написанный в стиле Node, так,
чтобы он получал обратный вызов для обязательства. Нам также понадобится функ
ция setTimeout, которая получает обратный вызов... но поскольку она появилась
задолго до Node, она не соответствует соглашению о передаче ошибок функциям
обратного вызова. Поэтому необходимо создать новую функцию ptimeout (promise
timeout - период обязательства).
Генераторы 247