Рассмотрим следующий код, который читает массив файлов последовательным / последовательным образом. readFiles возвращает обещание, которое разрешается только после того, как все файлы были прочитаны в последовательности.
var Q = require("q");
var readFile = function(file) {
... // Returns a promise.
};
var readFiles = function(files) {
var deferred = Q.defer();
var readSequential = function(index) {
if (index >= files.length) {
deferred.resolve();
} else {
readFile(files[index]).then(function() {
readSequential(index + 1);
});
}
};
readSequential(0); // Start!
return deferred.promise;
};
Код выше кода работает, но мне не нравится делать рекурсию для того, чтобы что-то происходило последовательно. Есть ли более простой способ перезаписи этого кода, так что мне не нужно использовать мою странную функцию readSequential?
Первоначально я попытался использовать Q.all, но это вызвало все readFile вызывает одновременное выполнение, чего я не хочу:
var readFiles = function(files) {
return Q.all(files.map(function(file) {
return readFile(file);
}));
};
Используйте Array.prototype.reduce и не забудьте обернуть свои обещания функцией, иначе они уже будут запущены!
// array of Promise providers
const providers = [
function(){
return Promise.resolve(1);
},
function(){
return Promise.resolve(2);
},
function(){
return Promise.resolve(3);
}
]
const seed = Promise.resolve(null);
const inSeries = function(providers){
return providers.reduce(function(a,b){
return a.then(b);
}, seed);
};
приятно и легко ... вы должны иметь возможность повторно использовать тот же семя для производительности и т. д.
Мой ответ основан на https://stackoverflow.com/a/31070150/7542429.
Promise.series = function series(arrayOfPromises) {
var results = [];
return arrayOfPromises.reduce(function(seriesPromise, promise) {
return seriesPromise.then(function() {
return promise
.then(function(result) {
results.push(result);
});
});
}, Promise.resolve())
.then(function() {
return results;
});
};
Это решение возвращает результаты в виде массива вроде Promise.all ().
Использование:
Promise.series([array of promises])
.then(function(results) {
// do stuff with results here
});
Я создал этот простой метод для объекта Promise:
Promise.sequence = function (chain) {
var results = [];
var entries = chain;
if (entries.entries) entries = entries.entries();
return new Promise(function (yes, no) {
var next = function () {
var entry = entries.next();
if(entry.done) yes(results);
else {
results.push(entry.value[1]().then(next, function() { no(results); } ));
}
};
next();
});
};
var todo = [];
todo.push(firstPromise);
if (someCriterium) todo.push(optionalPromise);
todo.push(lastPromise);
// Invoking them
Promise.sequence(todo)
.then(function(results) {}, function(results) {});
Лучшее в этом расширении объекта Promise заключается в том, что оно соответствует стилю обещаний. Promise.all и Promise.sequence вызываются одинаково, но имеют различную семантику.
Последовательное выполнение обещаний обычно не является очень хорошим способом использования обещаний. Обычно лучше использовать Promise.all, и пусть браузер запускает код как можно быстрее. Однако для него существуют реальные варианты использования - например, при написании мобильного приложения с использованием javascript.
Мне действительно понравился ответ @ joelnet, но для меня этот стиль кодирования немного сложнее переварить, поэтому я потратил пару дней, пытаясь понять, как я буду выражать то же самое решение более читаемым образом и это мой прием, просто с другим синтаксисом и некоторыми комментариями.
// first take your work
const urls = ['/url1', '/url2', '/url3', '/url4']
// next convert each item to a function that returns a promise
const functions = urls.map((url) => {
// For every url we return a new function
return () => {
return new Promise((resolve) => {
// random wait in milliseconds
const randomWait = parseInt((Math.random() * 1000),10)
console.log('waiting to resolve in ms', randomWait)
setTimeout(()=>resolve({randomWait, url}),randomWait)
})
}
})
const promiseReduce = (acc, next) => {
// we wait for the accumulator to resolve it's promise
return acc.then((accResult) => {
// and then we return a new promise that will become
// the new value for the accumulator
return next().then((nextResult) => {
// that eventually will resolve to a new array containing
// the value of the two promises
return accResult.concat(nextResult)
})
})
};
// the accumulator will always be a promise that resolves to an array
const accumulator = Promise.resolve([])
// we call reduce with the reduce function and the accumulator initial value
functions.reduce(promiseReduce, accumulator)
.then((result) => {
// let's display the final value here
console.log('=== The final result ===')
console.log(result)
})
Это небольшое изменение другого ответа выше. Использование собственных обещаний:
function inSequence(tasks) {
return tasks.reduce((p, task) => p.then(task), Promise.resolve())
}
Объяснение
Если у вас есть эти задачи [t1, t2, t3], то приведенное выше эквивалентно Promise.resolve().then(t1).then(t2).then(t3). Это поведение уменьшения.
Пояснение
Сначала вам нужно создать список задач! Задача - это функция, которая не принимает аргументов. Если вам нужно передать аргументы своей функции, используйте bind или другие методы для создания задачи. Например:
var tasks = files.map(file => processFile.bind(null, file))
inSequence(tasks).then(...)
Ваш подход не плох, но у него есть две проблемы: он проглатывает ошибки, и он использует Explicit Construction Antipattern.
Вы можете решить обе эти проблемы и сделать код чистым, в то время как все еще используя одну и ту же общую стратегию:
var Q = require("q");
var readFile = function(file) {
... // Returns a promise.
};
var readFiles = function(files) {
var readSequential = function(index) {
if (index < files.length) {
return readFile(files[index]).then(function() {
return readSequential(index + 1);
});
}
};
// using Promise.resolve() here in case files.length is 0
return Promise.resolve(readSequential(0)); // Start!
};
Этот вопрос старый, но мы живем в мире ES6 и функциональном JavaScript, поэтому давайте посмотрим, как мы можем улучшить.
Поскольку обещания выполняются немедленно, мы не можем просто создать массив обещаний , все они будут стрелять параллельно.
Вместо этого нам нужно создать массив функций, который возвращает обещание. Каждая функция будет выполняться последовательно, а затем запускает обещание внутри.
Мы можем решить это несколькими способами, но мой любимый способ - использовать reduce.
Он получает немного сложнее использовать reduce в сочетании с обещаниями, поэтому я сломал один лайнер на несколько менее удобоваримых укусов ниже.
Суть этой функции заключается в использовании reduce, начиная с начального значения из Promise.resolve([]) или обещание, содержащее пустой массив.
Это обещание будет передано в метод reduce как promise. Это ключ к последовательному объединению каждого обещания. Следующее обещание выполнить func, а когда then срабатывает, результаты конкатенируются и затем возвращается обещание, выполняя цикл reduce со следующей функцией обещания.
Как только все обещания
Пример ES6 (один лайнер)
/*
* serial executes Promises sequentially.
* @param {funcs} An array of funcs that return promises.
* @example
* const urls = ['/url1', '/url2', '/url3']
* serial(urls.map(url => () => $.ajax(url)))
* .then(console.log.bind(console))
*/
const serial = funcs =>
funcs.reduce((promise, func) =>
promise.then(result => func().then(Array.prototype.concat.bind(result))), Promise.resolve([]))
ES6 Пример (с разбивкой)
// broken down to for easier understanding
const concat = list => Array.prototype.concat.bind(list)
const promiseConcat = f => x => f().then(concat(x))
const promiseReduce = (acc, x) => acc.then(promiseConcat(x))
/*
* serial executes Promises sequentially.
* @param {funcs} An array of funcs that return promises.
* @example
* const urls = ['/url1', '/url2', '/url3']
* serial(urls.map(url => () => $.ajax(url)))
* .then(console.log.bind(console))
*/
const serial = funcs => funcs.reduce(promiseReduce, Promise.resolve([]))
Использование:
// first take your work
const urls = ['/url1', '/url2', '/url3', '/url4']
// next convert each item to a function that returns a promise
const funcs = urls.map(url => () => $.ajax(url))
// execute them serially
serial(funcs)
.then(console.log.bind(console))
Я использую следующий код для расширения объекта Promise. Он обрабатывает отказ от обещаний и возвращает массив результатов
Код
/*
Runs tasks in sequence and resolves a promise upon finish
tasks: an array of functions that return a promise upon call.
parameters: an array of arrays corresponding to the parameters to be passed on each function call.
context: Object to use as context to call each function. (The 'this' keyword that may be used inside the function definition)
*/
Promise.sequence = function(tasks, parameters = [], context = null) {
return new Promise((resolve, reject)=>{
var nextTask = tasks.splice(0,1)[0].apply(context, parameters[0]); //Dequeue and call the first task
var output = new Array(tasks.length + 1);
var errorFlag = false;
tasks.forEach((task, index) => {
nextTask = nextTask.then(r => {
output[index] = r;
return task.apply(context, parameters[index+1]);
}, e=>{
output[index] = e;
errorFlag = true;
return task.apply(context, parameters[index+1]);
});
});
// Last task
nextTask.then(r=>{
output[output.length - 1] = r;
if (errorFlag) reject(output); else resolve(output);
})
.catch(e=>{
output[output.length - 1] = e;
reject(output);
});
});
};
Код
function functionThatReturnsAPromise(n) {
return new Promise((resolve, reject)=>{
//Emulating real life delays, like a web request
setTimeout(()=>{
resolve(n);
}, 1000);
});
}
var arrayOfArguments = [['a'],['b'],['c'],['d']];
var arrayOfFunctions = (new Array(4)).fill(functionThatReturnsAPromise);
Promise.sequence(arrayOfFunctions, arrayOfArguments)
.then(console.log)
.catch(console.error);
Самое лучшее решение, которое я смог выяснить, было с bluebird обещаниями. Вы можете просто сделать Promise.resolve(files).each(fs.readFileAsync);, который гарантирует, что обещания разрешаются последовательно в порядке.
Мое предпочтительное решение:
function processArray(arr, fn) {
return arr.reduce(
(p, v) => p.then((a) => fn(v).then(r => a.concat([r]))),
Promise.resolve([])
);
}
Это не принципиально отличается от других, опубликованных здесь, но:
Применяет функцию к элементам последовательно. Решает массив массивов результатов. требуют async / await (поддержка по-прежнему весьма ограничена, около 2017) Использует функции стрелок; nice и conciseПример использования:
const numbers = [0, 4, 20, 100];
const multiplyBy3 = (x) => new Promise(res => res(x * 3));
// Prints [ 0, 12, 60, 300 ]
processArray(numbers, multiplyBy3).then(console.log);
Проверено на разумные текущие версии Chrome (v59) и NodeJS (v8.1.2).
Простая утилита для стандартного обещания Node.js:
function sequence(tasks, fn) {
return tasks.reduce((promise, task) => promise.then(() => fn(task)), Promise.resolve());
}
UPDATE
items-prom - это готовый к использованию пакет NPM, который делает то же самое.
На основании названия вопроса «Решение обещает одно за другим (т. е. в последовательности)?», мы могли бы понять, что ОП больше интересуется последовательной обработкой обещаний по урегулированию, чем последовательными вызовами как таковыми.
Этот ответ предлагается:
, чтобы продемонстрировать, что последовательные вызовы не нужны для последовательной обработки ответов. выставлять жизнеспособные альтернативные шаблоны посетителям этой страницы, включая OP, если он по-прежнему заинтересован более года спустя. несмотря на утверждение OP о том, что он не хочет делать вызовы одновременно, что может быть действительно так, но в равной степени может быть предположением, основанным на стремлении к последовательной обработке ответов, как следует из названия.Если одновременные вызовы действительно не нужны, см. ответ Benjamin Gruenbaum, в котором подробно рассматриваются последовательные вызовы (и т. д.).
Если, однако, вы заинтересованы (для повышения производительности) в шаблонах, которые позволяют одновременные вызовы, за которыми следует последовательная обработка ответов, затем, пожалуйста, прочитайте.
Заманчиво думать, что вы должны использовать Promise.all(arr.map(fn)).then(fn) (как я уже много раз делал), или модный саунд Promise lib (особенно Bluebird's), однако (с учетом этой статьи) шаблон arr.map(fn).reduce(fn) выполнит эту работу с преимуществами:
, чтобы продемонстрировать, что последовательные вызовы не нужны для последовательной обработки ответов. предоставляет гибкость для пропуска над ошибкой или остановки -on-error, в зависимости от того, что вы хотите с модой одной строки.Здесь он написан для Q.
var readFiles = function(files) {
return files.map(readFile) //Make calls in parallel.
.reduce(function(sequence, filePromise) {
return sequence.then(function() {
return filePromise;
}).then(function(file) {
//Do stuff with file ... in the correct sequence!
}, function(error) {
console.log(error); //optional
return sequence;//skip-over-error. To stop-on-error, `return error` (jQuery), or `throw error` (Promises/A+).
});
}, Q()).then(function() {
// all done.
});
};
per se
Ключевым моментом здесь является сокращение sequence обещание, в котором последовательности обработки обещаний readFile, но не их создания.
И как только вы впитаете это, возможно, слегка раздувая, когда вы понимаете, что этап .map() t действительно необходимо! Вся работа, параллельные вызовы плюс последовательная обработка в правильном порядке, может быть достигнута только с помощью reduce(), плюс дополнительное преимущество дальнейшей гибкости:
дает гибкость для пропусков, error или stop-on-error, в зависимости от того, что вы хотите с модемной строкой.Здесь это снова для Q.
var readFiles = function(files) {
return files.reduce(function(sequence, f) {
var filePromise = readFile(f);//Make calls in parallel. To call sequentially, move this line down one.
return sequence.then(function() {
return filePromise;
}).then(function(file) {
//Do stuff with file ... in the correct sequence!
}, function(error) {
console.log(error); //optional
return sequence;//Skip over any errors. To stop-on-error, `return error` (jQuery), or `throw error` (Promises/A+).
});
}, Q()).then(function() {
// all done.
});
};
Это основной рисунок. Если вы хотите также предоставить данные (например, файлы или некоторые их преобразования) для вызывающего, вам понадобится мягкий вариант.
Мне пришлось выполнить множество последовательных задач и использовать эти ответы для создания функции, которая будет заботиться о обработке любой последовательной задачи ...
function one_by_one(objects_array, iterator, callback) {
var start_promise = objects_array.reduce(function (prom, object) {
return prom.then(function () {
return iterator(object);
});
}, Promise.resolve()); // initial
if(callback){
start_promise.then(callback);
}else{
return start_promise;
}
}
Функция принимает 2 аргумента + 1 опционально. Первый аргумент - это массив, над которым мы будем работать. Второй аргумент - это сама задача, функция, которая возвращает обещание, следующая задача будет начата только тогда, когда это обещание будет разрешено. Третий аргумент - это обратный вызов для запуска, когда все задачи выполнены. Если обратный вызов не передан, функция возвращает обещание, которое оно создало, чтобы мы могли обработать конец.
Вот пример использования:
var filenames = ['1.jpg','2.jpg','3.jpg'];
var resize_task = function(filename){
//return promise of async resizing with filename
};
one_by_one(filenames,resize_task );
Надеюсь, что это кому-то поможет ...
Для этого просто в ES6:
function(files) {
// Create a new empty promise (don't do that with real people ;)
var sequence = Promise.resolve();
// Loop over each file, and add on a promise to the
// end of the 'sequence' promise.
files.forEach(function(file) {
// Chain one computation onto the sequence
sequence = sequence.then(function() {
return performComputation(file);
}).then(function(result) {
doSomething(result) // Resolves for each file, one at a time.
});
})
// This will resolve after the entire chain is resolved
return sequence;
}
Вы можете использовать эту функцию, которая получает promFactories List:
function executeSequentially(promiseFactories) {
var result = Promise.resolve();
promiseFactories.forEach(function (promiseFactory) {
result = result.then(promiseFactory);
});
return result;
}
Promise Factory - это просто простая функция, которая возвращает Promise:
function myPromiseFactory() {
return somethingThatCreatesAPromise();
}
Это работает, потому что обещание фабрики не создает обещание, пока его не попросят. Он работает так же, как и функция then - на самом деле, это одно и то же!
Вы вообще не хотите работать над множеством обещаний. По спецификации Promise, как только создается обещание, оно начинает выполняться. Итак, что вы действительно хотите, это массив обещающих фабрик ...
Если вы хотите узнать больше о Promises, вы должны проверить эту ссылку: https://pouchdb.com/2015/05/18/ мы-есть-а-проблема-с-promises.html
Если кому-то нужен гарантированный способ СТРОГО последовательного способа разрешения обещаний при выполнении операций CRUD, вы также можете использовать следующий код в качестве основы.
Пока вы добавляете «возврат» перед вызовом каждого функцию, описывающую Promise и использующую этот пример в качестве основы, следующий вызов функции .then () СОСТОЯТСЯ после завершения предыдущего:
getRidOfOlderShoutsPromise = () => {
return readShoutsPromise('BEFORE')
.then(() => {
return deleteOlderShoutsPromise();
})
.then(() => {
return readShoutsPromise('AFTER')
})
.catch(err => console.log(err.message));
}
deleteOlderShoutsPromise = () => {
return new Promise ( (resolve, reject) => {
console.log("in deleteOlderShouts");
let d = new Date();
let TwoMinuteAgo = d - 1000 * 90 ;
All_Shouts.deleteMany({ dateTime: {$lt: TwoMinuteAgo}}, function(err) {
if (err) reject();
console.log("DELETED OLDs at "+d);
resolve();
});
});
}
readShoutsPromise = (tex) => {
return new Promise( (resolve, reject) => {
console.log("in readShoutsPromise -"+tex);
All_Shouts
.find({})
.sort([['dateTime', 'ascending']])
.exec(function (err, data){
if (err) reject();
let d = new Date();
console.log("shouts "+tex+" delete PROMISE = "+data.length +"; date ="+d);
resolve(data);
});
});
}
Если вы хотите, вы можете использовать сокращение, чтобы сделать последовательное обещание, например:
[2,3,4,5,6,7,8,9].reduce((promises, page) => {
return promises.then((page) => {
console.log(page);
return Promise.resolve(page+1);
});
}, Promise.resolve(1));
он всегда будет работать последовательно.
Это касается того, как обрабатывать последовательность обещаний более общим образом, поддерживая динамические / бесконечные последовательности, основанные на реализации spex.sequence:
var $q = require("q");
var spex = require('spex')($q);
var files = []; // any dynamic source of files;
var readFile = function (file) {
// returns a promise;
};
function source(index) {
if (index < files.length) {
return readFile(files[index]);
}
}
function dest(index, data) {
// data = resolved data from readFile;
}
spex.sequence(source, dest)
.then(function (data) {
// finished the sequence;
})
.catch(function (error) {
// error;
});
Не только это решение будет работать с последовательностями любого размера, но вы можете легко добавить к нему spex.sequence .
Вот как я предпочитаю запускать задачи последовательно.
function runSerial() {
var that = this;
// task1 is a function that returns a promise (and immediately starts executing)
// task2 is a function that returns a promise (and immediately starts executing)
return Promise.resolve()
.then(function() {
return that.task1();
})
.then(function() {
return that.task2();
})
.then(function() {
console.log(" ---- done ----");
});
}
Как насчет случаев с большим количеством задач? Например, 10? function runSerial(tasks) {
var result = Promise.resolve();
tasks.forEach(task => {
result = result.then(() => task());
});
return result;
}