Как мне получить доступ к предыдущим результатам обещания в цепочке .then ()?

Я преобразовал свой код в обещания и построил замечательную длинную плоскую цепочку обещаний, состоящую из несколько .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?
    });
}

person Bergi    schedule 31.01.2015    source источник
comment
Это действительно интересный вопрос, и даже если он помечен как javascript, он актуален для других языков. Я просто использую разрыв цепочки ответов в java и jdeferred   -  person gontard    schedule 09.12.2015


Ответы (17)


Разорвать цепь

Когда вам нужно получить доступ к промежуточным значениям в вашей цепочке, вы должны разделить цепочку на те отдельные части, которые вам нужны. Вместо того, чтобы прикреплять один обратный вызов и каким-то образом пытаться использовать его параметр несколько раз, прикрепите несколько обратных вызовов к одному и тому же обещанию - везде, где вам нужно значение результата. Не забывайте, что обещание просто представляет (прокси) будущую ценность! После извлечения одного обещания из другого в линейной цепочке используйте комбинаторы обещаний, которые дает вам ваша библиотека, чтобы построить значение результата.

Это приведет к очень простому потоку управления, четкому составу функций и, следовательно, легкой модульности.

function getExample() {
    var a = promiseA(…);
    var b = a.then(function(resultA) {
        // some processing
        return promiseB(…);
    });
    return Promise.all([a, b]).then(function([resultA, resultB]) {
        // more processing
        return // something using both resultA and resultB
    });
}

Вместо деструктурирования параметра в обратном вызове после Promise.all, которое стало доступно только в ES6, в ES5 вызов then будет заменен изящным вспомогательным методом, который был предоставлен многими библиотеками обещаний (Q, Bluebird, когда ,…): .spread(function(resultA, resultB) { ….

Bluebird также имеет специальную join функцию, заменяющую эту комбинацию _6 _ + _ 7_ более простой (и более эффективная) конструкция:

…
return Promise.join(a, b, function(resultA, resultB) { … });
person Bergi    schedule 31.01.2015
comment
Выполняются ли функции внутри массива по порядку? - person scaryguy; 16.04.2015
comment
@scaryguy: В массиве нет функций, это обещания. promiseA и promiseB - это функции (возвращающие обещание). - person Bergi; 16.04.2015
comment
О да, на самом деле я имел в виду обещания :) Возможно, я не совсем понимаю обещания, извините. Итак, когда вы используете .all или .join, обещания возвращают значения по порядку, верно? - person scaryguy; 16.04.2015
comment
Да, гарантировано, что resultA происходит от a, а resultB исходит от b, независимо от порядка, в котором становятся доступны результаты. - person Bergi; 17.04.2015
comment
Ключевым моментом для меня было фактически вернуть данные из моего обратного вызова Promise.all.then. Раньше я возвращал Promise.all обратный вызов, но у меня был только console.log в моем .then обратном вызове. Это привело к тому, что данные, передаваемые в любые будущие обратные вызовы обещаний, стали undefined. - person Bryan Downing; 28.02.2016
comment
@Rhayene: Да, Bluebird, но также Q, когда и другие основные библиотеки обещаний реализуют его. Это становится менее важным с деструктуризацией в ES6. - person Bergi; 11.07.2016
comment
аааа, теперь понятно, что он не компилировался: D Я просто неправильно понял ваш ответ. Я запустил его с оператором спреда. - person Rhayene; 11.07.2016
comment
что, если он в цикле for? как обрабатывать аргументы .spread(function(resultA, resultB)? - person DragonKnight; 26.07.2017
comment
@DragonKnight Что в петле? Этот вопрос и все ответы предполагают, что у вас есть фиксированное количество обещаний, к результатам которых вы хотите получить доступ индивидуально. Если вы работаете с массивами произвольного размера, используйте массив результатов, который генерирует Promise.all. - person Bergi; 26.07.2017
comment
@Bergi, моя проблема в том, что я не знаю, сколько будет обещаний. я должен обработать большую цепочку обещаний и создать для каждого эластичный поисковый запрос. что я сделал сейчас, так это когда я возвращаю ответ, я прикрепляю данные, которые мне нужны для следующего запроса, например return {response:response, data1:"d1",data2:"d2"}, и получаю к ним доступ в ответе then(function(data)), но он стал очень уродливым. - person DragonKnight; 26.07.2017
comment
@DragonKnight Вы можете задать отдельный вопрос и включить туда свой код. Но в целом, когда вы не знаете, сколько элементов у вас будет, используйте массивы. - person Bergi; 26.07.2017
comment
@Roland Никогда не говорил, что это было :-) Этот ответ был написан в эпоху ES5, когда в стандарте не было никаких обещаний, и spread был очень полезен в этом шаблоне. Для более современных решений см. Принятый ответ. Однако я уже обновил ответ явной пересылки, и на самом деле нет веских причин не обновлять и этот. - person Bergi; 30.08.2017
comment
Какие-нибудь советы по именованию обещаний, когда вы сохраняете результат Promise.all в переменной, чтобы его можно было использовать повторно? Особенно, если это просто сделать несколько значений доступными для более позднего кода (как в этом ответе). - person tscizzle; 23.11.2018
comment
Вместо Promise.all вы также можете использовать liftP2 et al: liftP2 = f => p => q => p.then(x => q.then(y => f(x, y))). - person ; 05.12.2018
comment
@reify Нет, этого не следует делать, это принесет неприятности с отказами. - person Bergi; 05.12.2018
comment
Я не понимаю этого примера. Если есть цепочка операторов «то», которые требуют, чтобы значения распространялись по всей цепочке, я не вижу, как это решает проблему. Обещание, для которого требуется предыдущее значение, НЕ МОЖЕТ быть запущено (создано), пока это значение не присутствует. Кроме того, Promise.all () просто ожидает завершения всех обещаний в своем списке: он не устанавливает порядок. Поэтому мне нужно, чтобы каждая «следующая» функция имела доступ ко всем предыдущим значениям, и я не понимаю, как это делает ваш пример. Вы должны показать нам свой пример, потому что я этому не верю и не понимаю. - person David Spector; 27.08.2019
comment
@DavidSpector Это все еще цепочка, в которой обратный вызов связывается после promiseB(), привязанного к promiseA(), устанавливая порядок. Сохраняя отдельные обещания в переменных, использование Promise.all позволяет нам зависеть от значений из нескольких шагов цепочки, а не только от последнего. - person Bergi; 27.08.2019
comment
Понятно. Что ж, с тех пор я узнал, что новый синтаксис Async / Await включает автоматическое связывание аргументов, поэтому все аргументы доступны для всех асинхронных функций. Так усердно поработав, чтобы узнать, как использовать bind, для чего не нужно использовать Promise.all (), я решил полностью отказаться от Promises. - person David Spector; 28.08.2019
comment
@Bergi Возможно, я слишком долго читал этот пост и MDN, но действительно ли PromiseA и PromiseB обещают сами себя? Разве это не функции, возвращающие объекты, которые сами по себе являются обещаниями. И затем эти обещания приводят либо к разрешению, либо к отклонению методов выполняемых объектов? Это последний кусок головоломки, в котором я до сих пор не понимаю. - person ; 17.10.2019
comment
@TeeJ Извините, да, promiseA - это функция, возвращающая обещание, а promiseA() - это фактическое обещание. Как они выполняются или отклоняются, не имеет значения для целей этого вопроса. Если вам все еще неясно, являются ли resolve и reject методами, задайте новый вопрос, на который я отвечу более подробно. - person Bergi; 17.10.2019

ECMAScript Harmony

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

ECMAScript 8

Вам больше не нужен ни один вызов then или функция обратного вызова, так как в асинхронной функции (которая возвращает обещание при вызове) вы можете просто дождаться разрешения обещаний напрямую. Он также имеет произвольные управляющие структуры, такие как условия, циклы и предложения try-catch, но для удобства они нам здесь не нужны:

async function getExample() {
    var resultA = await promiseA(…);
    // some processing
    var resultB = await promiseB(…);
    // more processing
    return // something using both resultA and resultB
}

ECMAScript 6

Пока мы ждали ES8, мы уже использовали очень похожий синтаксис. ES6 поставляется с функциями генератора, которые позволяют разбивать выполнение на части по произвольно размещенным yield ключевым словам. Эти срезы можно запускать друг за другом, независимо, даже асинхронно - и это именно то, что мы делаем, когда хотим дождаться разрешения обещания перед выполнением следующего шага.

Существуют специальные библиотеки (например, co или task.js), но также многие библиотеки обещаний имеют вспомогательные функции (Q, Bluebird, когда,…), которые делают это пошаговое асинхронное выполнение для вас, когда вы даете им функцию генератора, которая дает обещания.

var getExample = Promise.coroutine(function* () {
//               ^^^^^^^^^^^^^^^^^ Bluebird syntax
    var resultA = yield promiseA(…);
    // some processing
    var resultB = yield promiseB(…);
    // more processing
    return // something using both resultA and resultB
});

Это работало в Node.js, начиная с версии 4.0, а также несколько браузеров (или их версий для разработчиков) относительно рано поддерживали синтаксис генератора.

ECMAScript 5

Однако, если вы хотите / должны быть обратно совместимы, вы не можете использовать их без транспилятора. Обе функции генератора и асинхронные функции поддерживаются текущим инструментарием, см., Например, документацию Babel на генераторы и асинхронные функции.

Кроме того, существует множество других языки компиляции в JS, предназначенные для упрощения асинхронного программирования. Обычно они используют синтаксис, аналогичный await (например, Iced CoffeeScript), но есть и другие которые имеют do-нотацию, подобную Haskell (например, LatteJs, монадический, PureScript или LispyScript).

person Bergi    schedule 31.01.2015
comment
@Bergi, вам нужно дождаться выполнения асинхронной функции Examle getExample () из внешнего кода? - person arisalexis; 28.08.2015
comment
@arisalexis: Да, getExample по-прежнему является функцией, которая возвращает обещание, работает так же, как функции в других ответах, но с более приятным синтаксисом. Вы можете await вызвать другую async функцию или связать .then() ее результат. - person Bergi; 29.08.2015
comment
Мне любопытно, почему вы ответили на свой вопрос сразу после того, как задали его? Здесь есть хорошее обсуждение, но мне любопытно. Может быть, вы сами нашли ответы после того, как спросили? - person granmoe; 06.03.2016
comment
@granmoe: Я специально разместил всю дискуссию как каноническую дублирующую цель - person Bergi; 06.03.2016
comment
Есть ли (не слишком трудоемкий) способ избежать использования Promise.coroutine (т.е. не использовать Bluebird или другую библиотеку, а только простой JS) в примере ECMAScript 6 с функцией генератора? Я имел в виду что-то вроде steps.next().value.then(steps.next)..., но это не сработало. - person a learner has no name; 31.08.2016
comment
@DanielMH Вы можете сделать это вручную без какой-либо вспомогательной функции, но это всегда трудоемко и чревато ошибками. Я абсолютно не могу это рекомендовать. Но если вы не можете использовать транспилятор, вы все равно будете использовать _1 _ / _ 2_. - person Bergi; 31.08.2016
comment
@Bergi Спасибо за быстрый ответ! Я надеялся, что найдется «простое» решение (подобное тому, что я указал: пошагово через генератор и на каждом повороте передается разрешенное значение последнего обещания), но похоже, что это не так. - person a learner has no name; 31.08.2016

Синхронный осмотр

Присвоение переменным значений обещаний для последующего использования с последующим получением их значений с помощью синхронной проверки. В примере используется метод .value() bluebird, но многие библиотеки предоставляют аналогичный метод.

function getExample() {
    var a = promiseA(…);

    return a.then(function() {
        // some processing
        return promiseB(…);
    }).then(function(resultB) {
        // a is guaranteed to be fulfilled here so we can just retrieve its
        // value synchronously
        var aValue = a.value();
    });
}

Это можно использовать для любого количества значений:

function getExample() {
    var a = promiseA(…);

    var b = a.then(function() {
        return promiseB(…)
    });

    var c = b.then(function() {
        return promiseC(…);
    });

    var d = c.then(function() {
        return promiseD(…);
    });

    return d.then(function() {
        return a.value() + b.value() + c.value() + d.value();
    });
}
person Esailija    schedule 31.01.2015
comment
Это мой любимый ответ: читаемый, расширяемый и минимальный выбор библиотечных или языковых функций. - person Jason; 15.04.2016
comment
@Jason: Эээ, минимальная зависимость от функций библиотеки? Синхронная проверка - это функция библиотеки, причем довольно нестандартная для загрузки. - person Bergi; 25.06.2016
comment
Я думаю, он имел в виду особенности библиотеки - person Kirk Sefchik; 16.08.2017

Вложенность (и) закрытия

Использование замыканий для поддержания объема переменных (в нашем случае параметров функции обратного вызова успеха) является естественным решением для JavaScript. С помощью обещаний мы можем произвольно вкладывать и сглаживать .then() обратные вызовы - они семантически эквивалентны, за исключением области действия внутреннего.

function getExample() {
    return promiseA(…).then(function(resultA) {
        // some processing
        return promiseB(…).then(function(resultB) {
            // more processing
            return // something using both resultA and resultB;
        });
    });
}

Конечно, это построение пирамиды отступов. Если отступ становится слишком большим, вы все равно можете применить старые инструменты, чтобы противостоять пирамиде гибели: модулируйте, используйте дополнительные имена функций и сгладить цепочку обещаний, как только вам больше не понадобится переменная.
Теоретически вы всегда можете избежать более двух уровней вложенности (сделав все замыкания явными), на практике используйте столько, сколько разумны.

function getExample() {
    // preprocessing
    return promiseA(…).then(makeAhandler(…));
}
function makeAhandler(…)
    return function(resultA) {
        // some processing
        return promiseB(…).then(makeBhandler(resultA, …));
    };
}
function makeBhandler(resultA, …) {
    return function(resultB) {
        // more processing
        return // anything that uses the variables in scope
    };
}

Вы также можете использовать вспомогательные функции для такого типа частичного приложения, например _.partial from Подчеркивание / lodash или собственный .bind() метод, чтобы еще больше уменьшить отступ:

function getExample() {
    // preprocessing
    return promiseA(…).then(handlerA);
}
function handlerA(resultA) {
    // some processing
    return promiseB(…).then(handlerB.bind(null, resultA));
}
function handlerB(resultA, resultB) {
    // more processing
    return // anything that uses resultA and resultB
}
person Bergi    schedule 31.01.2015
comment
Это же предложение дается как решение «расширенной ошибки № 4» в статье Нолана Лоусона о обещаниях pouchdb.com/2015/05/18/we-have-a-problem-with-promises.html. Это хорошее чтение. - person Robert; 04.03.2016
comment
Это в точности функция bind в Monads. Haskell предоставляет синтаксический сахар (do-notation), чтобы он выглядел как синтаксис async / await. - person zeronone; 06.08.2016

Явный сквозной

Подобно вложению обратных вызовов, этот метод основан на закрытии. Тем не менее, цепочка остается плоской - вместо передачи только последнего результата для каждого шага передается некоторый объект состояния. Эти объекты состояния накапливают результаты предыдущих действий, снова передавая все значения, которые потребуются позже, плюс результат текущей задачи.

function getExample() {
    return promiseA(…).then(function(resultA) {
        // some processing
        return promiseB(…).then(b => [resultA, b]); // function(b) { return [resultA, b] }
    }).then(function([resultA, resultB]) {
        // more processing
        return // something using both resultA and resultB
    });
}

Здесь эта маленькая стрелка b => [resultA, b] - это функция, которая закрывает resultA и передает массив обоих результатов следующему шагу. В котором используется синтаксис деструктуризации параметров, чтобы снова разбить его на отдельные переменные.

До того, как деструктуризация стала доступной в ES6, изящный вспомогательный метод .spread() был предоставлен многими библиотеками обещаний (Q, Bluebird, когда,…). Для использования в качестве .spread(function(resultA, resultB) { … требуется функция с несколькими параметрами - по одному для каждого элемента массива.

Конечно, необходимое здесь закрытие можно дополнительно упростить с помощью некоторых вспомогательных функций, например

function addTo(x) {
    // imagine complex `arguments` fiddling or anything that helps usability
    // but you get the idea with this simple one:
    return res => [x, res];
}

…
return promiseB(…).then(addTo(resultA));

В качестве альтернативы вы можете использовать Promise.all для создания обещания для массива:

function getExample() {
    return promiseA(…).then(function(resultA) {
        // some processing
        return Promise.all([resultA, promiseB(…)]); // resultA will implicitly be wrapped
                                                    // as if passed to Promise.resolve()
    }).then(function([resultA, resultB]) {
        // more processing
        return // something using both resultA and resultB
    });
}

И вы можете использовать не только массивы, но и объекты произвольной сложности. Например, с _.extend или _ 10_ в другой вспомогательной функции:

function augment(obj, name) {
    return function (res) { var r = Object.assign({}, obj); r[name] = res; return r; };
}

function getExample() {
    return promiseA(…).then(function(resultA) {
        // some processing
        return promiseB(…).then(augment({resultA}, "resultB"));
    }).then(function(obj) {
        // more processing
        return // something using both obj.resultA and obj.resultB
    });
}

Хотя этот шаблон гарантирует плоскую цепочку, а явные объекты состояния могут улучшить ясность, для длинной цепочки он станет утомительным. Особенно, когда вам нужно состояние только время от времени, вам все равно придется проходить его через каждый шаг. С этим фиксированным интерфейсом отдельные обратные вызовы в цепочке довольно тесно связаны и негибки для изменения. Это затрудняет выделение отдельных шагов, а обратные вызовы не могут быть предоставлены напрямую из других модулей - они всегда должны быть заключены в шаблонный код, который заботится о состоянии. Абстрактные вспомогательные функции, подобные приведенным выше, могут немного облегчить боль, но они всегда будут присутствовать.

person Bergi    schedule 31.01.2015
comment
Во-первых, я не думаю, что следует поощрять синтаксис без Promise.all (он не будет работать в ES6, когда его заменит деструктуризация, а переключение .spread на then часто дает людям неожиданные результаты. Что касается дополнений - я не уверен почему вам нужно использовать расширение - добавление вещей в прототип обещания не является приемлемым способом расширения обещаний ES6, которые должны быть расширены с помощью (в настоящее время не поддерживаемого) подкласса. - person Benjamin Gruenbaum; 31.01.2015
comment
@BenjaminGruenbaum: Что вы имеете в виду под опущением синтаксиса Promise.all? Ни один из методов в этом ответе не сломается с ES6. Переключение spread на деструктурирующий then также не должно иметь проблем. Re .prototype.augment: Я знал, что кто-то это заметит, мне просто нравилось исследовать возможности - собираюсь отредактировать. - person Bergi; 31.01.2015
comment
Под синтаксисом массива я имею в виду return [x,y]; }).spread(... вместо return Promise.all([x, y]); }).spread(..., который не изменится при замене распространения на деструктурирующий сахар es6, а также не будет странным пограничным случаем, когда обещания обрабатывают возвращаемые массивы иначе, чем все остальное. - person Benjamin Gruenbaum; 31.01.2015
comment
@BenjaminGruenbaum: x и y здесь простые значения, нет причин заключать их в Promise.all. Существуют ли какие-нибудь странные реализации обещаний, которые по-разному обрабатывают then обратные вызовы, возвращающие массивы? - person Bergi; 31.01.2015
comment
Да ладно - и да. Вышеупомянутое `return promiseB (…) .then (function (b) {return [resultA, b]});` можно записать как return [resultA, promiseB(...)];, если следующее, что вы сделаете, это .spread это - я не помню, был ли bluebird по-прежнему делает это, но определенно так и было. Кроме того, его всегда можно переписать как `return Promise.all [resultA, promiseB (...)]});` - person Benjamin Gruenbaum; 31.01.2015
comment
Да ладно, это можно было бы использовать здесь. Мне никогда не нравились эти неявные приведения к обещанию :-) Я дополню ответ версией .all. - person Bergi; 31.01.2015
comment
@BenjaminGruenbaum .spread() совпадает с .all().then(function(args){apply args...}); - из-за .all() вам не нужно Promise.all при использовании .spread() метода. - person Esailija; 02.02.2015
comment
@Esailija права, но я думаю, что такое поведение сложно и проблематично, потому что, когда люди будут использовать .then с деструктуризацией вместо .spread, факт. Spread действительно .all - это чудак здесь imo. - person Benjamin Gruenbaum; 02.02.2015
comment
@BenjaminGruenbaum На самом деле это не имеет значения, поскольку .spread на самом деле не нужен в 3.0 - функции обратного вызова с несколькими аргументами по умолчанию разрешаются только с первым аргументом. Хотя он используется в приведенном выше ответе, это, безусловно, самая неудобная из альтернатив. - person Esailija; 02.02.2015
comment
Это, наверное, лучший ответ. Обещания - это функционально-реактивное программирование, и это часто используемое решение. Например, у BaconJs есть #combineTemplate, который позволяет объединять результаты в объект, который передается по цепочке. - person U Avalos; 23.01.2016
comment
@UAvalos: Ну, combineTemplate больше похоже на Promise.join (ответ на разрыв цепочки), не так ли? - person Bergi; 23.01.2016
comment
стандартное обещание в javascript не имеет .spread, упоминание его вместо Promise.all и деструктуризация усложняют этот ответ, чем он должен быть. - person Capi Etheriel; 25.05.2017
comment
@CapiEtheriel Ответ был написан, когда ES6 не был таким распространенным, как сегодня. Да, может пора поменять примеры - person Bergi; 25.05.2017
comment
@UAvalos Хотя я согласен с тем, что это лучший ответ (с точки зрения FP), я не согласен с вашим сравнением. FRP - это события, поведение, потоки событий и подходящие комбинаторы. Единственное совпадение с Promise состоит в том, что оба они имеют дело с асинхронными потоками управления и имеют структуру, подобную монаде. - person ; 06.09.2017

Изменяемое контекстное состояние

Тривиальное (но неэлегантное и довольно подверженное ошибкам) ​​решение состоит в том, чтобы просто использовать переменные более высокого уровня (к которым имеют доступ все обратные вызовы в цепочке) и записывать в них значения результатов, когда вы их получаете:

function getExample() {
    var resultA;
    return promiseA(…).then(function(_resultA) {
        resultA = _resultA;
        // some processing
        return promiseB(…);
    }).then(function(resultB) {
        // more processing
        return // something using both resultA and resultB
    });
}

Вместо многих переменных можно также использовать (изначально пустой) объект, результаты которого сохраняются как динамически создаваемые свойства.

У этого решения есть несколько недостатков:

  • Изменяемое состояние некрасиво, и глобальные переменные - зло.
  • Этот шаблон не работает через границы функций, разбиение функций на модули сложнее, поскольку их объявления не должны покидать общую область видимости.
  • Объем переменных не препятствует доступу к ним до их инициализации. Это особенно вероятно для сложных конструкций обещаний (циклы, ветвление, исключения), где могут возникнуть условия гонки. Явная передача состояния, декларативный дизайн, обещающий поощрение, требует более чистого стиля кодирования, который может предотвратить это.
  • Необходимо правильно выбрать область действия этих общих переменных. Он должен быть локальным по отношению к исполняемой функции, чтобы предотвратить состояние гонки между несколькими параллельными вызовами, как это было бы, например, если бы состояние было сохранено в экземпляре.

Библиотека Bluebird поощряет использование переданного объекта, используя свой bind() метод для назначить объект контекста цепочке обещаний. Он будет доступен из каждой функции обратного вызова через неиспользуемый в противном случае this ключевое слово. Хотя свойства объекта более подвержены необнаруженным опечаткам, чем переменные, шаблон довольно умен:

function getExample() {
    return promiseA(…)
    .bind({}) // Bluebird only!
    .then(function(resultA) {
        this.resultA = resultA;
        // some processing
        return promiseB(…);
    }).then(function(resultB) {
        // more processing
        return // something using both this.resultA and resultB
    }).bind(); // don't forget to unbind the object if you don't want the
               // caller to access it
}

Этот подход можно легко смоделировать в библиотеках обещаний, которые не поддерживают .bind (хотя и более подробным образом и не могут использоваться в выражении):

function getExample() {
    var ctx = {};
    return promiseA(…)
    .then(function(resultA) {
        this.resultA = resultA;
        // some processing
        return promiseB(…);
    }.bind(ctx)).then(function(resultB) {
        // more processing
        return // something using both this.resultA and resultB
    }.bind(ctx));
}
person Bergi    schedule 31.01.2015
comment
.bind() не требуется для предотвращения утечки памяти - person Esailija; 31.01.2015
comment
@Esailija: Но разве возвращаемое обещание не содержит ссылку на объект контекста в противном случае? Хорошо, конечно, сборщик мусора обработает это позже; это не утечка, если обещание никогда не удаляется. - person Bergi; 31.01.2015
comment
Да, но обещания также содержат ссылки на их значения выполнения и причины ошибок ... но ничто не ссылается на обещание, поэтому это не имеет значения - person Esailija; 31.01.2015
comment
Я больше опасался, что, например, какое-то ajax().then(function(res) { this.response = res; return res.length; }) обещание было кэшировано, где интерес представляет только длина ответа (небольшое значение), но весь ответ (большое значение) хранится в памяти. Конечно, это не утечка памяти типа круговой ссылки, которая может сбить с толку gc движка (как мы знали из IE), а просто утечка данных для вызывающего. - person Bergi; 31.01.2015
comment
Пожалуйста, разбейте этот ответ на два, поскольку я почти проголосовал за преамбулу! Я думаю, что тривиальное (но неэлегантное и скорее подверженное ошибкам) ​​решение - это самое чистое и простое решение, поскольку оно больше не зависит от замыканий и изменяемого состояния, чем ваш принятый самоответ, но оно проще. Замыкания не являются глобальными и злыми. Аргументы, приведенные против этого подхода, не имеют для меня смысла с учетом исходной посылки. Какие проблемы с модуляризацией можно дать прекрасной длинной плоской цепочке обещаний? - person jib; 02.02.2015
comment
@jib: Я не уверен, на какие части мне следует разбить это. Он охватывает использование изменяемого состояния, доступ к которому осуществляется через закрытие, а не локально для каждого обратного вызова (да, это не глобальная область, но я не хотел вводить термин свободная переменная). Обратите внимание, что я критиковал не замыкания, а мутации. Проблемы с модуляризацией возникают, когда ваша цепочка обещаний становится слишком длинной, а ваши обратные вызовы становятся слишком большими, так что вы хотите отложить их в сторону. - person Bergi; 02.02.2015
comment
Как я уже сказал выше, обещания - это свет функционального реактивного программирования. Это антипаттерн в FRP - person U Avalos; 23.01.2016
comment
@UAvalos: Пожалуйста, проголосуйте против шаблона - я не могу этого сделать :-) - person Bergi; 23.01.2016
comment
Я не уверен, что называть это изменяемое состояние довольно точно, поскольку цель обещаний в первую очередь (или, по крайней мере, основного) - позволить писать асинхронный код так, как это было бы, в этом случае мы ' мы говорим о локальных переменных. Конечно, никто не считает, что присвоение значения локальной переменной нарушает принцип изменяемого состояния, не так ли? - person dmansfield; 30.01.2016
comment
@dmansfield: Да, может быть, но проблема с этим решением заключается в том, что а) переменная на самом деле не локальная б) нет временной мертвой зоны, как если бы она была выше let - и в асинхронном коде у вас гораздо больше шансов получить к нему доступ, прежде чем инициализировать его. - person Bergi; 30.01.2016
comment
Когда вы используете именованные функции, которые определены вне области видимости, решение «bind(ctx)» действительно очень удобно. Пример: var ctx = {lightOn: true); getBrightnessAsync().then(calculateNewBrightness.bind(ctx)).then(setBrightnessAsync).then(sendResponseAsync.bind(ctx)). - person a learner has no name; 01.09.2016

Менее резкое изложение «Изменяемого контекстного состояния»

Использование объекта с локальной областью видимости для сбора промежуточных результатов в цепочке обещаний - разумный подход к поставленному вами вопросу. Рассмотрим следующий фрагмент:

function getExample(){
    //locally scoped
    const results = {};
    return promiseA(paramsA).then(function(resultA){
        results.a = resultA;
        return promiseB(paramsB);
    }).then(function(resultB){
        results.b = resultB;
        return promiseC(paramsC);
    }).then(function(resultC){
        //Resolve with composite of all promises
        return Promise.resolve(results.a + results.b + resultC);
    }).catch(function(error){
        return Promise.reject(error);
    });
}
  • Глобальные переменные - это плохо, поэтому в этом решении используется переменная с локальной областью видимости, которая не причиняет вреда. Он доступен только внутри функции.
  • Мутабельное состояние некрасиво, но оно не изменяет состояние некрасивым образом. Уродливое изменяемое состояние традиционно относится к изменению состояния аргументов функции или глобальных переменных, но этот подход просто изменяет состояние переменной с локальной областью видимости, которая существует с единственной целью агрегирования результатов обещания ... переменной, которая умрет простой смертью. как только обещание разрешится.
  • Промежуточным обещаниям не запрещен доступ к состоянию объекта результатов, но это не представляет некоторого пугающего сценария, при котором одно из обещаний в цепочке не сработает и саботирует ваши результаты. Ответственность за установку значений на каждом шаге обещания ограничивается этой функцией, и общий результат будет либо правильным, либо неправильным ... это не будет какой-то ошибкой, которая возникнет спустя годы в производстве (если вы не намерены !)
  • Это не представляет собой сценарий состояния гонки, который может возникнуть при параллельном вызове, потому что новый экземпляр переменной результатов создается для каждого вызова функции getExample.
person Jay    schedule 24.03.2017

Узел 7.4 теперь поддерживает вызовы async / await с флагом гармонии.

Попробуй это:

async function getExample(){

  let response = await returnPromise();

  let response2 = await returnPromise2();

  console.log(response, response2)

}

getExample()

и запустите файл с помощью:

node --harmony-async-await getExample.js

Просто, насколько это возможно!

person Anthony    schedule 21.01.2017

Другой ответ, использующий babel-node версию ‹6

Использование async - await

npm install -g [email protected]

example.js:

async function getExample(){

  let response = await returnPromise();

  let response2 = await returnPromise2();

  console.log(response, response2)

}

getExample()

Затем запустите babel-node example.js и вуаля!

person Anthony    schedule 20.11.2015
comment
Да, сразу после того, как выложил свой. Тем не менее, я собираюсь оставить его, потому что он объясняет, как на самом деле начать работу с использованием ES7, а не просто сказать, что когда-нибудь ES7 будет доступен. - person Anthony; 21.11.2015
comment
Хорошо, я должен обновить свой ответ, чтобы сказать, что экспериментальные плагины для этих уже здесь. - person Bergi; 22.11.2015

В этот день я тоже встречу несколько вопросов вроде тебя. Наконец-то я нашел хорошее решение вопроса, оно простое и удобное для чтения. Я надеюсь это тебе поможет.

Согласно how-to-chain-javascript-promises

хорошо, давайте посмотрим на код:

const firstPromise = () => {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            console.log('first promise is completed');
            resolve({data: '123'});
        }, 2000);
    });
};

const secondPromise = (someStuff) => {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            console.log('second promise is completed');
            resolve({newData: `${someStuff.data} some more data`});
        }, 2000);
    });
};

const thirdPromise = (someStuff) => {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            console.log('third promise is completed');
            resolve({result: someStuff});
        }, 2000);
    });
};

firstPromise()
    .then(secondPromise)
    .then(thirdPromise)
    .then(data => {
        console.log(data);
    });
person yzfdjzwl    schedule 25.07.2017
comment
На самом деле это не отвечает на вопрос о том, как получить доступ к предыдущим результатам в цепочке. - person Bergi; 25.07.2017
comment
Каждое обещание может получить предыдущее значение, что вы имеете в виду? - person yzfdjzwl; 26.07.2017
comment
Взгляните на код вопроса. Цель не в том, чтобы получить результат обещания, которое вызывается .then, а в том, чтобы получить результат до этого. Например. thirdPromise доступ к результату firstPromise. - person Bergi; 26.07.2017
comment
Это поведение обещаний по умолчанию, я боюсь, что это не отвечает на исходный вопрос, извините. - person Raúl Núñez de Arenas Coronado; 22.02.2021

Я не собираюсь использовать этот шаблон в своем собственном коде, так как я не большой поклонник использования глобальных переменных. Однако в крайнем случае это сработает.

Пользователь - это обещанная модель Mongoose.

var globalVar = '';

User.findAsync({}).then(function(users){
  globalVar = users;
}).then(function(){
  console.log(globalVar);
});
person Anthony    schedule 11.08.2015
comment
Обратите внимание, что этот шаблон уже подробно описан в ответе с изменяемым контекстным состоянием (а также почему это некрасиво - я тоже не большой поклонник) - person Bergi; 11.08.2015
comment
В вашем случае шаблон кажется бесполезным. Вам вообще не нужен globalVar, просто сделайте User.findAsync({}).then(function(users){ console.log(users); mongoose.connection.close() });? - person Bergi; 11.08.2015
comment
Мне лично это не нужно в моем собственном коде, но пользователю может потребоваться выполнить больше асинхронности во второй функции, а затем взаимодействовать с исходным вызовом Promise. Но, как уже упоминалось, в этом случае я буду использовать генераторы. :) - person Anthony; 11.08.2015

Другой ответ, используя последовательный исполнитель nsynjs:

function getExample(){

  var response1 = returnPromise1().data;

  // promise1 is resolved at this point, '.data' has the result from resolve(result)

  var response2 = returnPromise2().data;

  // promise2 is resolved at this point, '.data' has the result from resolve(result)

  console.log(response, response2);

}

nynjs.run(getExample,{},function(){
    console.log('all done');
})

Обновление: добавлен рабочий пример

function synchronousCode() {
     var urls=[
         "https://ajax.googleapis.com/ajax/libs/jquery/1.7.0/jquery.min.js",
         "https://ajax.googleapis.com/ajax/libs/jquery/1.8.0/jquery.min.js",
         "https://ajax.googleapis.com/ajax/libs/jquery/1.9.0/jquery.min.js"
     ];
     for(var i=0; i<urls.length; i++) {
         var len=window.fetch(urls[i]).data.text().data.length;
         //             ^                   ^
         //             |                   +- 2-nd promise result
         //             |                      assigned to 'data'
         //             |
         //             +-- 1-st promise result assigned to 'data'
         //
         console.log('URL #'+i+' : '+urls[i]+", length: "+len);
     }
}

nsynjs.run(synchronousCode,{},function(){
    console.log('all done');
})
<script src="https://rawgit.com/amaksr/nsynjs/master/nsynjs.js"></script>

person amaksr    schedule 10.06.2017

При использовании bluebird вы можете использовать метод .bind для совместного использования переменных в цепочке обещаний:

somethingAsync().bind({})
.spread(function (aValue, bValue) {
    this.aValue = aValue;
    this.bValue = bValue;
    return somethingElseAsync(aValue, bValue);
})
.then(function (cValue) {
    return this.aValue + this.bValue + cValue;
});

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

http://bluebirdjs.com/docs/api/promise.bind.html

person alphakevin    schedule 12.06.2016
comment
Обратите внимание, что этот шаблон уже подробно описан в ответе с изменяемым контекстным состоянием - person Bergi; 12.06.2016

function getExample() {
    var retA, retB;
    return promiseA(…).then(function(resultA) {
        retA = resultA;
        // Some processing
        return promiseB(…);
    }).then(function(resultB) {
        // More processing
        //retA is value of promiseA
        return // How do I gain access to resultA here?
    });
}

легкий способ: D

person Minh Giang    schedule 03.03.2017
comment
Вы заметили этот ответ? - person Bergi; 03.03.2017

Я думаю, вы можете использовать хеш RSVP.

Что-то вроде того, что показано ниже:

    const mainPromise = () => {
        const promise1 = new Promise((resolve, reject) => {
            setTimeout(() => {
                console.log('first promise is completed');
                resolve({data: '123'});
            }, 2000);
        });

        const promise2 = new Promise((resolve, reject) => {
            setTimeout(() => {
                console.log('second promise is completed');
                resolve({data: '456'});
            }, 2000);
        });

        return new RSVP.hash({
              prom1: promise1,
              prom2: promise2
          });

    };


   mainPromise()
    .then(data => {
        console.log(data.prom1);
        console.log(data.prom2);
    });
person Vishu    schedule 29.08.2017
comment
Да, это то же самое, что и Promise.all решение, только с объектом вместо массива. - person Bergi; 29.08.2017

Решение:

Вы можете явно поместить промежуточные значения в область видимости в любой более поздней функции 'then', используя 'bind'. Это хорошее решение, которое не требует изменения принципа работы промисов и требует всего лишь одной или двух строк кода для распространения значений, как если бы ошибки уже были распространены.

Вот полный пример:

// Get info asynchronously from a server
function pGetServerInfo()
    {
    // then value: "server info"
    } // pGetServerInfo

// Write into a file asynchronously
function pWriteFile(path,string)
    {
    // no then value
    } // pWriteFile

// The heart of the solution: Write formatted info into a log file asynchronously,
// using the pGetServerInfo and pWriteFile operations
function pLogInfo(localInfo)
    {
    var scope={localInfo:localInfo}; // Create an explicit scope object
    var thenFunc=p2.bind(scope); // Create a temporary function with this scope
    return (pGetServerInfo().then(thenFunc)); // Do the next 'then' in the chain
    } // pLogInfo

// Scope of this 'then' function is {localInfo:localInfo}
function p2(serverInfo)
    {
    // Do the final 'then' in the chain: Writes "local info, server info"
    return pWriteFile('log',this.localInfo+','+serverInfo);
    } // p2

Это решение можно вызвать следующим образом:

pLogInfo("local info").then().catch(err);

(Примечание: была протестирована более сложная и полная версия этого решения, но не эта версия примера, поэтому в ней могла быть ошибка.)

person David Spector    schedule 27.08.2019
comment
Это похоже на тот же образец, что и в ответе вложенных (и) закрытий - person Bergi; 28.08.2019
comment
Это действительно похоже. С тех пор я узнал, что новый синтаксис Async / Await включает автоматическое связывание аргументов, поэтому все аргументы доступны для всех асинхронных функций. Я отказываюсь от обещаний. - person David Spector; 28.08.2019
comment
_1 _ / _ 2_ по-прежнему означает использование обещаний. Вы можете отказаться от then звонков с обратными вызовами. - person Bergi; 28.08.2019
comment
это только у меня, или ручное управление областью видимости примерно в 10 раз более примитивно, чем ручное управление памятью? какого черта это нужно? это выглядит ужасно. - person domoarigato; 04.10.2020
comment
На самом деле довольно просто делать что-то после такой асинхронной операции: promise.then1.then2.then3.catch. И хотя вы можете передать только одно значение из одной части этой цепочки в следующую часть, это значение может быть массивом или объектом, содержащим любое количество подзначений! И это еще более естественно, если вы используете асинхронные функции, потому что await можно использовать для ожидания завершения каждой асинхронной операции без каких-либо цепочек обещаний! Таким образом, асинхронное программирование может быть очень элегантным и компактным. - person David Spector; 05.10.2020

Что я узнал о обещаниях, так это то, что их следует использовать только как возвращаемые значения, по возможности избегайте ссылок на них. Синтаксис async / await особенно удобен для этого. Сегодня все новейшие браузеры и узлы поддерживают его: https://caniuse.com/#feat=async-functions, это простое поведение, и код похож на чтение синхронного кода, забудьте о обратных вызовах ...

В случаях, когда мне действительно нужно ссылаться на обещания, это когда создание и разрешение происходят в независимых / не связанных местах. Поэтому вместо искусственной ассоциации и, вероятно, прослушивателя событий, чтобы разрешить «отдаленное» обещание, я предпочитаю выставлять обещание как отложенное, что в следующем коде реализует его в действительном es5.

/**
 * Promise like object that allows to resolve it promise from outside code. Example:
 *
```
class Api {
  fooReady = new Deferred<Data>()
  private knower() {
    inOtherMoment(data=>{
      this.fooReady.resolve(data)
    })
  }
}
```
 */
var Deferred = /** @class */ (function () {
  function Deferred(callback) {
    var instance = this;
    this.resolve = null;
    this.reject = null;
    this.status = 'pending';
    this.promise = new Promise(function (resolve, reject) {
      instance.resolve = function () { this.status = 'resolved'; resolve.apply(this, arguments); };
      instance.reject = function () { this.status = 'rejected'; reject.apply(this, arguments); };
    });
    if (typeof callback === 'function') {
      callback.call(this, this.resolve, this.reject);
    }
  }
  Deferred.prototype.then = function (resolve) {
    return this.promise.then(resolve);
  };
  Deferred.prototype.catch = function (r) {
    return this.promise.catch(r);
  };
  return Deferred;
}());

переведено из моего машинописного проекта:

https://github.com/cancerberoSgx/misc-utils-of-mine/blob/2927c2477839f7b36247d054e7e50abe8a41358b/misc-utils-of-mine-generic/src/promise.ts#LOF31

Для более сложных случаев я часто использую эти небольшие служебные программы без проверки и ввода зависимостей. p-map использовалась несколько раз. Я думаю, он охватил большинство случаев использования:

https://github.com/sindresorhus?utf8=%E2%9C%93&tab=repositories&q=promise&type=source&language=

person cancerbero    schedule 12.09.2019
comment
Похоже, вы предлагаете либо изменяемое контекстное состояние, либо синхронная проверка? - person Bergi; 13.09.2019
comment
@bergi Впервые я возглавляю эти имена. добавление в список спасибо. Я знаю такого рода самосознательные обещания по имени Deferred - BTW, реализация - это просто обещание с завернутым решением. Мне часто нужен этот шаблон в тех случаях, когда ответственность за создание и выполнение обещания не зависит, поэтому нет необходимости связывать их только для выполнения обещания. Я адаптировал, но не для вашего примера, а использовал класс, но, возможно, эквивалент. - person cancerbero; 13.09.2019