Javascript 一个接一个地解决承诺(即按顺序)?
考虑以下以串行/顺序方式读取文件数组的代码Javascript 一个接一个地解决承诺(即按顺序)?,javascript,promise,q,sequential,serial-processing,Javascript,Promise,Q,Sequential,Serial Processing,考虑以下以串行/顺序方式读取文件数组的代码readFiles返回一个承诺,只有在按顺序读取所有文件后才能解析该承诺 var readFile = function(file) { ... // Returns a promise. }; var readFiles = function(files) { return new Promise((resolve, reject) => { var readSequential = function(index) {
readFiles
返回一个承诺,只有在按顺序读取所有文件后才能解析该承诺
var readFile = function(file) {
... // Returns a promise.
};
var readFiles = function(files) {
return new Promise((resolve, reject) => {
var readSequential = function(index) {
if (index >= files.length) {
resolve();
} else {
readFile(files[index]).then(function() {
readSequential(index + 1);
}).catch(reject);
}
};
readSequential(0); // Start with the first file!
});
};
上面的代码可以工作,但我不喜欢为了使事情按顺序发生而必须进行递归。是否有一种更简单的方法可以重新编写此代码,这样我就不必使用奇怪的readSequential
函数
最初我尝试使用Promise.all
,但这导致所有readFile
调用同时发生,这不是我想要的:
var readFiles = function(files) {
return Promise.all(files.map(function(file) {
return readFile(file);
}));
};
更新2017:如果环境支持,我会使用异步函数:
async function readFiles(files) {
for(const file of files) {
await readFile(file);
}
};
async function* readFiles(files) {
for(const file of files) {
yield await readFile(file);
}
};
如果愿意,可以使用异步生成器(如果您的环境支持)将文件的读取推迟到需要时:
更新:再想想-我可能会使用for循环:
var readFiles = function(files) {
var p = Promise.resolve(); // Q() in q
files.forEach(file =>
p = p.then(() => readFile(file));
);
return p;
};
或者更紧凑地使用reduce:
var readFiles = function(files) {
return files.reduce((p, file) => {
return p.then(() => readFile(file));
}, Promise.resolve()); // initial
};
在其他promise库(如when和Bluebird)中,您有用于此的实用方法
例如,蓝鸟是:
var Promise = require("bluebird");
var fs = Promise.promisifyAll(require("fs"));
var readAll = Promise.resolve(files).map(fs.readFileAsync,{concurrency: 1 });
// if the order matters, you can use Promise.each instead and omit concurrency param
readAll.then(function(allFileContents){
// do stuff to read files.
});
尽管没有理由不使用async Wait today。我能找到的最好的解决方案是使用
bluebird
承诺。您只需执行Promise.resolve(文件).each(fs.readFileAsync)代码>,它保证承诺按顺序得到解决。以下是我喜欢按顺序运行任务的方式。
有更多任务的案例如何?比如,10
我在Promise对象上创建了这个简单的方法:
创建Promise.sequence方法并将其添加到Promise对象
用法:
对Promise对象的这种扩展最好的一点是,它与Promise的样式一致。Promise.all和Promise.sequence的调用方式相同,但语义不同
小心
承诺的顺序运行通常不是使用承诺的好方法。通常最好使用Promise.all,让浏览器尽可能快地运行代码。然而,它也有实际的使用案例——例如,当使用javascript编写移动应用程序时。根据问题的标题“一个接一个(即按顺序)解决承诺”,我们可能会理解OP对结算承诺的顺序处理比顺序调用本身更感兴趣
答案如下:
- 证明顺序调用对于响应的顺序处理不是必需的
- 向本页面的访问者公开可行的替代模式——包括OP,如果一年后他仍然感兴趣的话
- 尽管OP声称他不想同时打电话,这可能是真的,但同样可能是基于对响应的顺序处理的愿望的假设,正如标题所示
如果真的不需要并发调用,请参阅Benjamin Grunbaum的答案,其中全面介绍了顺序调用(etc)
但是,如果您对允许并发调用然后顺序处理响应的模式感兴趣(为了提高性能),请继续阅读
很容易让人想到你必须使用Promise.all(arr.map(fn))。然后(fn)
(我已经做过很多次)或Promise-lib的花式糖(尤其是蓝鸟的),但是(归功于)一个arr.map(fn)。reduce(fn)
模式将完成这项工作,其优点是:
- 与任何promise库(甚至是jQuery的预兼容版本)一起使用-仅使用
。然后使用()
- 提供跳过错误或在错误时停止的灵活性,无论您希望使用哪种单行mod
这是为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.
});
};
注意:只有一个片段,Q()
,是特定于Q的。对于jQuery,您需要确保readFile()返回jQuery承诺。有了A+libs,外国承诺将被同化
这里的关键是reduce的序列
promise,它对readFile
承诺的处理进行排序,而不是它们的创建
一旦你了解了这一点,当你意识到.map()
阶段实际上并不必要时,你可能会有点激动人心!整个作业(并行调用加上按正确顺序进行的串行处理)都可以通过reduce()
单独完成,再加上进一步灵活的附加优势:
- 只需移动一行即可将并行异步调用转换为串行异步调用,这在开发过程中可能非常有用
在这里,又是为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.
});
};
这是基本模式。如果您还想向调用者交付数据(如文件或文件的某些转换),则需要一种温和的变体。这是对如何以更通用的方式处理承诺序列的扩展,支持动态/无限序列,基于实现:
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;
});
var$q=require(“q”);
var spex=需要('spex')($q);
var files=[];//任何动态文件源;
var readFile=函数(文件){
//回报承诺;
};
函数源(索引){
if(索引
此解决方案不仅适用于任何大小的序列,而且您可以轻松地添加到其中。要在ES6中简单地执行此操作,请执行以下操作:
函数(文件){
//创建一个新的空洞承诺(不要对真实的人这样做;)
var sequence=Promise.resolve();
//循环遍历每个文件,并向
//“顺序”承诺的结束。
files.forEach(文件=>{
//将一个计算链接到序列上
序列=
序列
.然后(()=>性能计算(文件
var todo = [];
todo.push(firstPromise);
if (someCriterium) todo.push(optionalPromise);
todo.push(lastPromise);
// Invoking them
Promise.sequence(todo)
.then(function(results) {}, function(results) {});
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.
});
};
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.
});
};
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;
});
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;
}
}
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 );
/*
* 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([]))
// 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))
function executeSequentially(promiseFactories) {
var result = Promise.resolve();
promiseFactories.forEach(function (promiseFactory) {
result = result.then(promiseFactory);
});
return result;
}
function myPromiseFactory() {
return somethingThatCreatesAPromise();
}
/*
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);
[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));
function sequence(tasks, fn) {
return tasks.reduce((promise, task) => promise.then(() => fn(task)), Promise.resolve());
}
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!
};
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.series([array of promises])
.then(function(results) {
// do stuff with results here
});
function inSequence(tasks) {
return tasks.reduce((p, task) => p.then(task), Promise.resolve())
}
var tasks = files.map(file => processFile.bind(null, file))
inSequence(tasks).then(...)
function processArray(arr, fn) {
return arr.reduce(
(p, v) => p.then((a) => fn(v).then(r => a.concat([r]))),
Promise.resolve([])
);
}
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);
// 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)
})
// array of Promise providers
const providers = [
function(){
return Promise.resolve(1);
},
function(){
return Promise.resolve(2);
},
function(){
return Promise.resolve(3);
}
]
const inSeries = function(providers){
const seed = Promise.resolve(null);
return providers.reduce(function(a,b){
return a.then(b);
}, seed);
};
const providers = [
function(v){
return Promise.resolve(v+1);
},
function(v){
return Promise.resolve(v+2);
},
function(v){
return Promise.resolve(v+3);
}
]
const inSeries = function(providers, initialVal){
if(providers.length < 1){
return Promise.resolve(null)
}
return providers.reduce((a,b) => a.then(b), providers.shift()(initialVal));
};
inSeries(providers, 1).then(v => {
console.log(v); // 7
});
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);
});
});
}
function downloadFile(fileUrl) { ... } // This function return a Promise
async function main()
{
var filesList = [...];
for (const file of filesList) {
await downloadFile(file);
}
}
function downloadFile(fileUrl) { ... } // This function return a Promise
function downloadRecursion(filesList, index)
{
index = index || 0;
if (index < filesList.length)
{
downloadFile(filesList[index]).then(function()
{
index++;
downloadRecursion(filesList, index); // self invocation - recursion!
});
}
else
{
return Promise.resolve();
}
}
function main()
{
var filesList = [...];
downloadRecursion(filesList);
}
const BlueBird = require('bluebird');
BlueBird.each(files, fs.readFileAsync);
["a","b","c"].map(x => returnsPromise(x))
["a","b","c"].map(x => () => returnsPromise(x))
["a", "b", "c"].map(x => () => returnsPromise(x))
.reduce(
(before, after) => before.then(_ => after()),
Promise.resolve()
)
(function() {
function sleep(ms) {
return new Promise(function(resolve) {
setTimeout(function() {
return resolve();
}, ms);
});
}
function serial(arr, index, results) {
if (index == arr.length) {
return Promise.resolve(results);
}
return new Promise(function(resolve, reject) {
if (!index) {
index = 0;
results = [];
}
return arr[index]()
.then(function(d) {
return resolve(d);
})
.catch(function(err) {
return reject(err);
});
})
.then(function(result) {
console.log("here");
results.push(result);
return serial(arr, index + 1, results);
})
.catch(function(err) {
throw err;
});
}
const a = [5000, 5000, 5000];
serial(a.map(x => () => sleep(x)));
})();
const promiseSequence = require('promise-sequence');
return promiseSequence(arr.map(el => () => doPromise(el)));
/*
{ status: 'fulfilled', value: 0 },
{ status: 'rejected', reason: 1 },
{ status: 'fulfilled', value: 2 }
*/