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

650

Я реструктурировал свой код к обещаниям и создал замечательную длинную цепочку плоских обещаний , состоящую из нескольких .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?
    });
}
Берги
источник
2
Этот вопрос действительно интересен, и даже если он помечен javascript, он актуален на другом языке. Я просто использую ответ «разорвать цепь» в java и jdeferred
gontard

Ответы:

377

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

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

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

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функцию для замены этой комбинации Promise.all+ spreadна более простую (и более эффективную) конструкцию:


return Promise.join(a, b, function(resultA, resultB) {  });
Берги
источник
1
Функции внутри массива выполняются по порядку?
страшно
6
@scaryguy: в массиве нет функций, это обещания. promiseAи promiseBявляются (обещающими-возвращающими) функциями здесь.
Берги
2
@Roland Никогда не говорил, что это был :-) Этот ответ был написан в эпоху ES5, где в стандарте вообще не было обещаний, и spreadбыл очень полезен в этом паттерне. Для более современных решений см. Принятый ответ. Тем не менее, я уже обновил ответ с явным прохождением , и на самом деле нет веской причины не обновлять и этот ответ .
Берги
1
@reify Нет, вы не должны этого делать , это приведет к проблемам с отказами.
Берги
1
Я не понимаю этот пример. Если есть цепочка утверждений «тогда», которые требуют распространения значений по всей цепочке, я не вижу, как это решает проблему. Обещание, которое требует, чтобы предыдущее значение НЕ МОЖЛО быть запущено (создано), пока это значение не присутствует. Кроме того, Promise.all () просто ожидает завершения всех обещаний в своем списке: он не навязывает заказ. Поэтому мне нужно, чтобы каждая «следующая» функция имела доступ ко всем предыдущим значениям, и я не вижу, как это делает ваш пример. Вы должны провести нас через ваш пример, потому что я не верю этому или не понимаю.
Дэвид Спектор
238

ECMAScript Гармония

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

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 ), но есть и другие, которые имеют Haskell-подобные doобозначения (например, LatteJs , monadic , PureScript или LispyScript ).

Берги
источник
@ Bergi, вам нужно дождаться асинхронной функции, например, getExample () из внешнего кода?
arisalexis
@arisalexis: Да, getExampleэто все еще функция, которая возвращает обещание, работает так же, как функции в других ответах, но с более приятным синтаксисом. Вы могли awaitбы вызвать другую asyncфункцию или связать .then()ее с результатом.
Берги
1
Мне любопытно, почему вы ответили на свой вопрос сразу после того, как задали его? Здесь есть хорошая дискуссия, но мне любопытно. Может быть, вы нашли свои ответы самостоятельно после того, как спросили?
Granmoe
@granmoe: я специально выложил всю дискуссию в качестве канонической повторяющейся цели
Берги
Есть ли (не слишком трудоемкий) способ избежать использования Promise.coroutine (т.е. не использовать Bluebird или другую библиотеку, а только простой JS) в примере ECMAScript 6 с функцией генератора? Я имел в виду нечто подобное, steps.next().value.then(steps.next)...но это не сработало.
ученик не имеет имени
102

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

Присваивание переменным обещаний для последующих необходимых значений и последующее получение их значения посредством синхронной проверки. В примере используется .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();
    });
}
Esailija
источник
6
Это мой любимый ответ: удобочитаемое, расширяемое и минимальное использование библиотечных или языковых функций
Джейсон
13
@Jason: « Минимальная опора на библиотечные функции »? Синхронный осмотр - это библиотечная функция, причем совершенно нестандартная для загрузки.
Берги
2
Я думаю, что он имел в виду специфические особенности библиотеки
deathgaze
54

Вложенные (и) замыкания

Использование замыканий для поддержки области видимости переменных (в нашем случае, параметров функции обратного вызова success) является естественным решением 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;
        });
    });
}

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

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из Underscore / 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
}
Берги
источник
5
Это же предложение даются в качестве решения «Advanced ошибки # 4» в статье Нолана Лоусона на обещания pouchdb.com/2015/05/18/we-have-a-problem-with-promises.html . Это хорошее чтение.
Роберт
2
Это именно та bindфункция, которая есть в Monads. Haskell предоставляет синтаксический сахар (do-notation), чтобы он выглядел как синтаксис async / await.
Зеронон
50

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

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

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или Object.assignв другой вспомогательной функции:

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
    });
}

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

Берги
источник
Во-первых, я не думаю, что Promise.allследует поощрять синтаксис, опускающий аргумент (он не будет работать в ES6, когда деструктуризация заменит его, а переключение .spreadна a thenдает людям часто неожиданные результаты. Что касается дополнения - я не уверен, зачем вам это нужно использовать augment - добавление вещей в прототип обещаний не является приемлемым способом расширения обещаний ES6, которые должны быть расширены с помощью (в настоящее время не поддерживается) подклассов
Бенджамин Грюнбаум
@BenjaminGruenbaum: Что вы подразумеваете под " опущением синтаксисаPromise.all "? Ни один из методов в этом ответе не сломается с ES6. Переключение spreadна деструктуризацию также thenне должно иметь проблем. Re .prototype.augment: я знал, что кто-то заметит это, я просто любил исследовать возможности - собирался отредактировать это.
Берги
По синтаксису массива я имею в виду return [x,y]; }).spread(...вместо того , return Promise.all([x, y]); }).spread(...который не будет меняться при замене распространения на ES6 деструктурирующего сахара и также не будет странный край случай , когда обещания лечить возвращения массивов в отличие от всего остального.
Бенджамин Грюнбаум
3
Это, наверное, лучший ответ. Обещания - это «функционально-реактивное программирование», и это часто используется в качестве решения. Например, BaconJs имеет #combineTemplate, который позволяет объединять результаты в объект, который передается по цепочке
U Avalos
1
@CapiEtheriel Ответ был написан, когда ES6 не был таким распространенным, как сегодня. Да, может быть, пришло время обменяться примерами
Берги
35

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

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

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));
}
Берги
источник
.bind()не нужен для предотвращения утечки памяти
Esailija
@Esailija: Но в противном случае возвращенное обещание не содержит ссылку на объект контекста? Хорошо, конечно, сборщик мусора справится с этим позже; это не «утечка», если обещание никогда не будет выполнено.
Берги
Да, но обещания также содержат ссылку на их значения выполнения и причины ошибок ... но ничто не содержит ссылку на обещание, так что это не имеет значения
Esailija
4
Пожалуйста, разбейте этот ответ на две части, поскольку я почти проголосовал за преамбулу! Я думаю, что «тривиальное (но не элегантное и довольно подверженное ошибкам) ​​решение» является наиболее чистым и простым решением, поскольку оно не зависит больше от замыканий и изменчивого состояния, чем ваш принятый самоответ, но все же проще. Закрытия не являются ни глобальными, ни злыми. Аргументы против такого подхода не имеют смысла для меня, учитывая предпосылку. Какие проблемы модульности можно создать в «замечательной длинной цепочке с плоскими обещаниями»?
Джиб
2
Как я уже говорил выше, Promises - это «функционально-реактивное программирование». Это анти-паттерн в FRP
U Avalos
15

Менее резкое вращение "изменяемого контекстуального состояния"

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

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.
сойка
источник
1
По крайней мере, избегайте Promiseконструктора antipattern !
Берги
Спасибо @Bergi, я даже не осознавал, что это анти-паттерн, пока ты не упомянул об этом!
Jay
Это хороший обходной путь для устранения ошибки, связанной с обещаниями. Я использовал ES5 и не хотел добавлять другую библиотеку для работы с обещаниями.
Нилаканта Сингх Део
8

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

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

async function getExample(){

  let response = await returnPromise();

  let response2 = await returnPromise2();

  console.log(response, response2)

}

getExample()

и запустите файл с:

node --harmony-async-await getExample.js

Просто, как может быть!

Энтони
источник
8

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

В соответствии с обещаниями о том, как цепочка Javascript

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

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);
    });
yzfdjzwl
источник
4
Это на самом деле не отвечает на вопрос о том, как получить доступ к предыдущим результатам в цепочке.
Берги
2
Каждое обещание может получить предыдущее значение, каково ваше значение?
yzfdjzwl
1
Посмотрите на код в вопросе. Цель не в том, чтобы получить результат обещанного обещания .then, а в результате до этого. Например, thirdPromiseдоступ к результату firstPromise.
Берги
6

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

С помощью async - await

npm install -g babel@5.6.14

example.js:

async function getExample(){

  let response = await returnPromise();

  let response2 = await returnPromise2();

  console.log(response, response2)

}

getExample()

Тогда беги babel-node example.jsи вуаля!

Энтони
источник
1
Да, я сделал, сразу после того, как я отправил мой. Тем не менее, я собираюсь оставить это, потому что он объясняет, как на самом деле начать работать с ES7, а не просто сказать, что когда-нибудь ES7 будет доступен.
Энтони
1
Да, я должен обновить свой ответ, чтобы сказать, что "экспериментальные" плагины для них уже здесь.
Берги
2

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

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

var globalVar = '';

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

Другой ответ, используя последовательного исполнителя 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>

amaksr
источник
1

При использовании 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

alphakevin
источник
Обратите внимание, что этот шаблон уже детализирован в ответе
изменяемого
1
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

Минь Гианг
источник
Вы заметили этот ответ ?
Берги
1

Я думаю, что вы можете использовать хэш 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);
    });
Вишу
источник
Да, это то же самое, что и Promise.allрешение , только с объектом вместо массива.
Берги
0

Решение:

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

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

// 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);

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

Дэвид Спектор
источник
Это похоже на тот же шаблон, что и в ответе о вложении (и) замыканий
Берги
Это выглядит похоже. С тех пор я узнал, что новый синтаксис Async / Await включает автоматическое связывание аргументов, поэтому все аргументы доступны для всех асинхронных функций. Я отказываюсь от Обещаний.
Дэвид Спектор
async/ awaitвсе еще означает использование обещаний. Вы можете отказаться от thenзвонков с обратными вызовами.
Берги
-1

Что я узнаю об обещаниях - это использовать их только как возвращаемые значения, избегая ссылок на них, если это возможно. Синтаксис 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#L31

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

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

cancerbero
источник
Похоже, вы предлагаете либо изменчивое контекстное состояние, либо синхронную проверку ?
Берги
@bergi В первый раз я возглавляю эти имена. Добавляю в список спасибо. Я знаю такие обещания, основанные на самосознании, по имени Deferred - кстати, реализация - это просто обещание с решимостью. Я часто нуждаюсь в этом шаблоне в тех случаях, когда ответственность за создание и разрешение обещаний независима, поэтому нет необходимости связывать их просто для разрешения обещания. Я адаптировался, но не для вашего примера, а используя класс, но, возможно, эквивалентный.
Cancebero