Узел JS обещают.все и forEach
У меня есть массив, как структура, которая предоставляет асинхронные методы. Асинхронный метод вызывает возвращаемые структуры массива, которые в свою очередь предоставляют больше асинхронных методов. Я создаю еще один объект JSON для хранения значений, полученных из этой структуры, и поэтому мне нужно быть осторожным в отслеживании ссылок в обратных вызовах.
я закодировал решение грубой силы, но я хотел бы узнать более идиоматическое или чистое решение.
- шаблон должен быть повторяемым для n уровень вложенности.
- мне нужно использовать обещание.все или некоторые подобные методы, чтобы определить, когда разрешить заключительную процедуру.
- не каждый элемент обязательно будет включать в себя асинхронный вызов. Так что во вложенном обещании.все, что я не могу просто сделать назначения моим элементам массива JSON на основе индекса. Тем не менее, мне нужно использовать что-то вроде обещания.все во вложенном forEach, чтобы убедиться, что все назначения свойств были сделаны до разрешения вложенного обычный.
- Я использую bluebird promise lib, но это не требование
вот некоторый код -
var jsonItems = [];
items.forEach(function(item){
var jsonItem = {};
jsonItem.name = item.name;
item.getThings().then(function(things){
// or Promise.all(allItemGetThingCalls, function(things){
things.forEach(function(thing, index){
jsonItems[index].thingName = thing.name;
if(thing.type === 'file'){
thing.getFile().then(function(file){ //or promise.all?
jsonItems[index].filesize = file.getSize();
3 ответа:
это довольно просто с некоторыми простыми правилами:
- когда вы создаете обещание в
then
, вернуть - любое обещание, которое вы не вернете, не будет ждать снаружи.- всякий раз, когда вы создаете несколько обещаний,
.all
их - таким образом, он ждет всех обещаний, и ни одна ошибка от любого из них не замолкает.- всякий раз, когда вы гнездо
then
s, вы обычно можете вернуться в средний -then
цепи обычно не более 1 уровня глубиной.- всякий раз, когда вы выполняете IO, это должно быть с обещанием - либо он должен быть в обещании, либо он должен использовать обещание, чтобы сигнализировать о его завершении.
и некоторые советы:
- отображение лучше сделать с
.map
чем сfor/push
- если вы сопоставляете значения с функцией,map
позволяет кратко выразить понятие применения действия одно за другим и агрегирование результатов.- параллелизм лучше, чем последовательное выполнение, если это бесплатно - лучше делать все одновременно и ждать их
Promise.all
чем выполнять вещи один за другим-каждый ждет перед следующим.хорошо, так что давайте начнем:
var items = [1, 2, 3, 4, 5]; var fn = function asyncMultiplyBy2(v){ // sample async action return new Promise(resolve => setTimeout(() => resolve(v * 2), 100)); }; // map over forEach since it returns var actions = items.map(fn); // run the function over all items // we now have a promises array and we want to wait for it var results = Promise.all(actions); // pass array of promises results.then(data => // or just .then(console.log) console.log(data) // [2, 4, 6, 8, 10] ); // we can nest this of course, as I said, `then` chains: var res2 = Promise.all([1, 2, 3, 4, 5].map(fn)).then( data => Promise.all(data.map(fn)) ).then(function(data){ // the next `then` is executed after the promise has returned from the previous // `then` fulfilled, in this case it's an aggregate promise because of // the `.all` return Promise.all(data.map(fn)); }).then(function(data){ // just for good measure return Promise.all(data.map(fn)); }); // now to get the results: res2.then(function(data){ console.log(data); // [16, 32, 48, 64, 80] });
вот простой пример использования reduce. Он работает последовательно, поддерживает порядок вставки и не требует Bluebird.
/** * * @param items An array of items. * @param fn A function that accepts an item from the array and returns a promise. * @returns {Promise} */ function forEachPromise(items, fn) { return items.reduce(function (promise, item) { return promise.then(function () { return fn(item); }); }, Promise.resolve()); }
и использовать его так:
var items = ['a', 'b', 'c']; function logItem(item) { return new Promise((resolve, reject) => { process.nextTick(() => { console.log(item); resolve(); }) }); } forEachPromise(items, logItem).then(() => { console.log('done'); });
мы нашли полезным отправить дополнительный контекст в цикл. Контекст является необязательным и общим для всех итераций.
function forEachPromise(items, fn, context) { return items.reduce(function (promise, item) { return promise.then(function () { return fn(item, context); }); }, Promise.resolve()); }
ваша функция обещания будет выглядеть так:
function logItem(item, context) { return new Promise((resolve, reject) => { process.nextTick(() => { console.log(item); context.itemCount++; resolve(); }) }); }
У меня была такая же ситуация. Я решил, используя два обещания.Все.)(
Я думаю, что это было действительно хорошее решение, поэтому я опубликовал его на npm:https://www.npmjs.com/package/promise-foreach
Я думаю, что ваш код будет что-то вроде этого
var promiseForeach = require('promise-foreach') var jsonItems = []; promiseForeach.each(jsonItems, [function (jsonItems){ return new Promise(function(resolve, reject){ if(jsonItems.type === 'file'){ jsonItems.getFile().then(function(file){ //or promise.all? resolve(file.getSize()) }) } }) }], function (result, current) { return { type: current.type, size: jsonItems.result[0] } }, function (err, newList) { if (err) { console.error(err) return; } console.log('new jsonItems : ', newList) })