Я реструктурировал свой код к обещаниям и создал замечательную длинную цепочку плоских обещаний , состоящую из нескольких .then()
обратных вызовов. В конце я хочу вернуть несколько составных значений и получить доступ к нескольким промежуточным результатам обещаний . Однако значения разрешения в середине последовательности не входят в область действия последнего обратного вызова. Как получить к ним доступ?
function getExample() {
return promiseA(…).then(function(resultA) {
// Some processing
return promiseB(…);
}).then(function(resultB) {
// More processing
return // How do I gain access to resultA here?
});
}
javascript
, он актуален на другом языке. Я просто использую ответ «разорвать цепь» в java и jdeferredОтветы:
Разорвать цепь
Когда вам нужно получить доступ к промежуточным значениям в вашей цепочке, вы должны разбить вашу цепочку на части, которые вам нужны. Вместо того, чтобы прикреплять один обратный вызов и каким-либо образом пытаться использовать его параметр несколько раз, прикрепите несколько обратных вызовов к одному и тому же обещанию - везде, где вам нужно значение результата. Не забывайте, что обещание просто представляет (прокси) будущую ценность ! Далее, чтобы получить одно обещание от другого в линейной цепочке, используйте комбинаторы обещаний, которые вам дает ваша библиотека, для построения значения результата.
Это приведет к очень простому потоку управления, четкому составу функций и, следовательно, легкой модульности.
Вместо деструктуризации параметра в функции обратного вызова после того,
Promise.all
что стал доступен только с ES6, в ES5then
вызов будет заменен изящным вспомогательным методом , который был предоставлен многими библиотеками обещания ( Q , Bluebird , когда , ...):.spread(function(resultA, resultB) { …
.Bluebird также имеет специальную
join
функцию для замены этой комбинацииPromise.all
+spread
на более простую (и более эффективную) конструкцию:источник
promiseA
иpromiseB
являются (обещающими-возвращающими) функциями здесь.spread
был очень полезен в этом паттерне. Для более современных решений см. Принятый ответ. Тем не менее, я уже обновил ответ с явным прохождением , и на самом деле нет веской причины не обновлять и этот ответ .ECMAScript Гармония
Конечно, эта проблема была признана дизайнерами языков. Они проделали большую работу, и предложение об асинхронных функциях наконец-то превратилось в
ECMAScript 8
Вам больше не нужна ни одна
then
функция вызова или обратного вызова, так как в асинхронной функции (которая возвращает обещание при вызове) вы можете просто дождаться разрешения обещаний напрямую. Он также имеет произвольные управляющие структуры, такие как условия, циклы и предложения try-catch, но для удобства они нам здесь не нужны:ECMAScript 6
Пока мы ждали ES8, мы уже использовали очень похожий синтаксис. ES6 поставляется с функциями генератора , которые позволяют разбить выполнение на части при произвольно размещенных
yield
ключевых словах. Эти фрагменты можно запускать друг за другом независимо, даже асинхронно, и это именно то, что мы делаем, когда хотим дождаться разрешения обещания, прежде чем выполнять следующий шаг.Существуют выделенные библиотеки (например, co или task.js ), но также многие библиотеки обещаний имеют вспомогательные функции ( Q , Bluebird , когда ...), которые выполняют это асинхронное пошаговое выполнение для вас, когда вы даете им функцию генератора, которая дает обещания.
Это работало в Node.js, начиная с версии 4.0, также некоторые браузеры (или их версии для разработчиков) относительно раньше поддерживали синтаксис генератора.
ECMAScript 5
Однако, если вы хотите / должны быть обратно совместимыми, вы не можете использовать их без транспилятора. Обе функции генератора и асинхронные функции поддерживаются текущим инструментарием, см., Например, документацию Babel о генераторах и асинхронных функциях .
И затем, есть также много других языков компиляции в JS , которые предназначены для облегчения асинхронного программирования. Обычно они используют синтаксис, похожий на
await
(например, Iced CoffeeScript ), но есть и другие, которые имеют Haskell-подобныеdo
обозначения (например, LatteJs , monadic , PureScript или LispyScript ).источник
getExample
это все еще функция, которая возвращает обещание, работает так же, как функции в других ответах, но с более приятным синтаксисом. Вы моглиawait
бы вызвать другуюasync
функцию или связать.then()
ее с результатом.steps.next().value.then(steps.next)...
но это не сработало.Синхронный осмотр
Присваивание переменным обещаний для последующих необходимых значений и последующее получение их значения посредством синхронной проверки. В примере используется
.value()
метод bluebird, но многие библиотеки предоставляют аналогичный метод.Это можно использовать для любого количества значений:
источник
Вложенные (и) замыкания
Использование замыканий для поддержки области видимости переменных (в нашем случае, параметров функции обратного вызова success) является естественным решением JavaScript. С помощью обещаний мы можем произвольно вкладывать и выравнивать
.then()
обратные вызовы - они семантически эквивалентны, за исключением области действия внутреннего.Конечно, это строительство пирамиды отступов. Если отступ становится слишком большим, вы все равно можете применить старые инструменты для противодействия пирамиде doom : modularize, использовать дополнительные именованные функции и сгладить цепочку обещаний, как только вам больше не понадобится переменная.
Теоретически, вы всегда можете избежать более двух уровней вложенности (сделав все замыкания явными), на практике используйте столько, сколько разумно.
Вы также можете использовать вспомогательные функции для этого вида частичного применения , например,
_.partial
из Underscore / lodash или нативного.bind()
метода , для дальнейшего уменьшения отступа:источник
bind
функция, которая есть в Monads. Haskell предоставляет синтаксический сахар (do-notation), чтобы он выглядел как синтаксис async / await.Явный сквозной
Подобно вложению обратных вызовов, этот метод основан на замыканиях. Тем не менее, цепочка остается плоской - вместо передачи только самого последнего результата, некоторый объект состояния передается для каждого шага. Эти объекты состояния накапливают результаты предыдущих действий, передавая все значения, которые понадобятся позже, плюс результат текущей задачи.
Здесь эта маленькая стрелка
b => [resultA, b]
- это функция, которая закрываетсяresultA
и передает массив обоих результатов на следующий шаг. Который использует синтаксис деструктурирования параметров, чтобы снова разбить его на отдельные переменные.До того, как деструктуризация стала доступна в ES6, во
.spread()
многих библиотеках обещаний ( Q , Bluebird , когда ...) был предоставлен отличный вспомогательный метод, вызываемый . Для этого используется функция с несколькими параметрами - по одному для каждого элемента массива.spread(function(resultA, resultB) { …
.Конечно, это замыкание, необходимое здесь, может быть дополнительно упрощено некоторыми вспомогательными функциями, например,
Кроме того, вы можете использовать
Promise.all
для создания обещания для массива:И вы можете использовать не только массивы, но и произвольно сложные объекты. Например, с
_.extend
илиObject.assign
в другой вспомогательной функции:Хотя этот шаблон гарантирует плоскую цепочку, а явные объекты состояния могут улучшить четкость, он станет утомительным для длинной цепочки. Особенно, когда вам нужно государство только время от времени, вы все равно должны проходить его через каждый шаг. С этим фиксированным интерфейсом отдельные обратные вызовы в цепочке довольно тесно связаны и негибки для изменения. Это усложняет выделение отдельных шагов, и обратные вызовы не могут быть предоставлены напрямую из других модулей - они всегда должны быть заключены в стандартный код, который заботится о состоянии. Абстрактные вспомогательные функции, подобные приведенным выше, могут немного облегчить боль, но она всегда будет присутствовать.
источник
Promise.all
следует поощрять синтаксис, опускающий аргумент (он не будет работать в ES6, когда деструктуризация заменит его, а переключение.spread
на athen
дает людям часто неожиданные результаты. Что касается дополнения - я не уверен, зачем вам это нужно использовать augment - добавление вещей в прототип обещаний не является приемлемым способом расширения обещаний ES6, которые должны быть расширены с помощью (в настоящее время не поддерживается) подклассовPromise.all
"? Ни один из методов в этом ответе не сломается с ES6. Переключениеspread
на деструктуризацию такжеthen
не должно иметь проблем. Re .prototype.augment: я знал, что кто-то заметит это, я просто любил исследовать возможности - собирался отредактировать это.return [x,y]; }).spread(...
вместо того ,return Promise.all([x, y]); }).spread(...
который не будет меняться при замене распространения на ES6 деструктурирующего сахара и также не будет странный край случай , когда обещания лечить возвращения массивов в отличие от всего остального.Изменяемое контекстное состояние
Тривиальное (но не элегантное и довольно ошибочное) решение состоит в том, чтобы просто использовать переменные более высокого уровня (к которым имеют доступ все обратные вызовы в цепочке) и записать в них значения результатов, когда вы их получите:
Вместо многих переменных можно также использовать (изначально пустой) объект, для которого результаты сохраняются в виде динамически создаваемых свойств.
Это решение имеет несколько недостатков:
Библиотека Bluebird поощряет использование переданного объекта, используя их
bind()
метод для назначения объекта контекста цепочке обещаний. Он будет доступен из каждой функции обратного вызова через неиспользуемоеthis
ключевое слово . Хотя свойства объекта более подвержены необнаруженным опечаткам, чем переменным, шаблон довольно умный:Этот подход может быть легко смоделирован в библиотеках обещаний, которые не поддерживают .bind (хотя и несколько более многословно и не могут использоваться в выражении):
источник
.bind()
не нужен для предотвращения утечки памятиМенее резкое вращение "изменяемого контекстуального состояния"
Использование локального объекта для сбора промежуточных результатов в цепочке обещаний - разумный подход к поставленному вами вопросу. Рассмотрим следующий фрагмент:
источник
Promise
конструктора antipattern !Узел 7.4 теперь поддерживает асинхронные / ожидающие вызовы с флагом гармонии.
Попробуй это:
и запустите файл с:
node --harmony-async-await getExample.js
Просто, как может быть!
источник
В эти дни я тоже встречался с такими вопросами, как ты. Наконец-то я нашел хорошее решение с помощью квеста, его просто и приятно читать. Я надеюсь это тебе поможет.
В соответствии с обещаниями о том, как цепочка Javascript
хорошо, давайте посмотрим на код:
источник
.then
, а в результате до этого. Например,thirdPromise
доступ к результатуfirstPromise
.Другой ответ, используя
babel-node
версию <6С помощью
async - await
npm install -g babel@5.6.14
example.js:
Тогда беги
babel-node example.js
и вуаля!источник
Я не собираюсь использовать этот шаблон в своем собственном коде, так как я не большой поклонник использования глобальных переменных. Однако в крайнем случае это будет работать.
Пользователь - обещанная модель Mongoose.
источник
globalVar
вообще, просто сделатьUser.findAsync({}).then(function(users){ console.log(users); mongoose.connection.close() });
?Другой ответ, используя последовательного исполнителя nsynjs :
Обновление: добавлен рабочий пример
источник
При использовании bluebird вы можете использовать
.bind
метод для обмена переменными в цепочке обещаний:пожалуйста, проверьте эту ссылку для получения дополнительной информации:
http://bluebirdjs.com/docs/api/promise.bind.html
источник
простой способ: D
источник
Я думаю, что вы можете использовать хэш RSVP.
Что-то вроде как ниже:
источник
Promise.all
решение , только с объектом вместо массива.Решение:
Вы можете поместить промежуточные значения в область видимости в любой более поздней функции 'then' явно, используя 'bind'. Это хорошее решение, которое не требует изменения того, как работает Promises, и требует только одну или две строки кода для распространения значений так же, как ошибки уже распространены.
Вот полный пример:
Это решение может быть вызвано следующим образом:
(Примечание: была протестирована более сложная и полная версия этого решения, но не эта примерная версия, поэтому в ней может быть ошибка.)
источник
async
/await
все еще означает использование обещаний. Вы можете отказаться отthen
звонков с обратными вызовами.Что я узнаю об обещаниях - это использовать их только как возвращаемые значения, избегая ссылок на них, если это возможно. Синтаксис async / await особенно полезен для этого. Сегодня все последние браузеры и узлы поддерживают это: https://caniuse.com/#feat=async-functions , это простое поведение, а код похож на чтение синхронного кода, забудьте про обратные вызовы ...
В тех случаях, когда мне нужно ссылаться на обещания, это когда создание и разрешение происходят в независимых / не связанных местах. Таким образом, вместо искусственной ассоциации и, возможно, прослушивателя событий, чтобы разрешить «отдаленное» обещание, я предпочитаю выставлять обещание как отложенное, которое в следующем коде реализует его в действительном es5.
передал форму моего машинописного проекта:
https://github.com/cancerberoSgx/misc-utils-of-mine/blob/2927c2477839f7b36247d054e7e50abe8a41358b/misc-utils-of-mine-generic/src/promise.ts#L31
В более сложных случаях я часто использую эти маленькие обещающие утилиты без проверенных и набранных зависимостей. p-карта была полезна несколько раз. Я думаю, что он охватил большинство случаев использования:
https://github.com/sindresorhus?utf8=%E2%9C%93&tab=repositories&q=promise&type=source&language=
источник