考虑下面以串行/顺序方式读取文件数组的代码。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。但是这会导致所有的readFile调用并发发生,这不是我想要的:
var readFiles = function(files) {
return Promise.all(files.map(function(file) {
return readFile(file);
}));
};
我在Promise对象上创建了这个简单的方法:
创建并添加承诺。sequence方法添加到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的风格一致。的承诺。一切和承诺。序列以同样的方式调用,但具有不同的语义。
谨慎
连续运行承诺通常不是使用承诺的好方法。通常使用Promise会更好。所有这些,并让浏览器尽可能快地运行代码。然而,它也有一些实际的用例——例如在使用javascript编写移动应用程序时。
我不得不运行大量的顺序任务,并使用这些答案来伪造一个函数,将照顾处理任何顺序任务…
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个可选参数。第一个参数是我们将要处理的数组。第二个参数是任务本身,一个返回承诺的函数,只有当这个承诺解决时,下一个任务才会开始。第三个参数是在所有任务完成后运行的回调。如果没有传递回调,则函数返回它创建的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 );
希望它能节省一些时间…
我喜欢的解决方案:
function processArray(arr, fn) {
return arr.reduce(
(p, v) => p.then((a) => fn(v).then(r => a.concat([r]))),
Promise.resolve([])
);
}
它与这里发表的其他文章没有本质区别,但是:
将函数应用于序列中的项
解析为结果数组
不需要async/await(支持仍然非常有限,大约在2017年)
使用箭头函数;非常简洁
使用示例:
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)上测试。
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
};
在其他承诺库(如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 await。
如果你愿意,你可以用reduce来做出一个连续的承诺,例如:
[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));
它总是按顺序工作。
如果其他人在执行CRUD操作时需要一种有保证的严格顺序的方法来解析promise,您也可以使用以下代码作为基础。
只要你在调用每个函数之前添加'return',描述一个Promise,并使用这个例子作为基础,下一个.then()函数调用将在前一个函数完成后consistent启动:
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);
});
});
}