考虑下面以串行/顺序方式读取文件数组的代码。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);
}));
};
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。
这是上面另一个答案的轻微变化。使用原生承诺:
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(...)
这是对如何以更通用的方式处理承诺序列的扩展,支持基于spex的动态/无限序列。序列实现:
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;
});
这种解决方案不仅适用于任何大小的序列,而且还可以轻松地向其添加数据节流和负载平衡。
下面是我的Angular/TypeScript方法,使用RxJS:
给定一个URL字符串数组,使用from函数将其转换为一个可观察对象。
使用管道包装Ajax请求、即时响应逻辑、任何所需的延迟和错误处理。
在管道内部,使用concatMap序列化请求。否则,使用Javascript forEach或map会同时发出请求。
使用RxJS ajax进行调用,并在每次调用返回后添加所需的延迟。
工作示例:https://stackblitz.com/edit/rxjs-bnrkix?file=index.ts
代码看起来像这样(我留下了一些额外的内容,这样你可以选择保留或丢弃什么):
import { ajax } from 'rxjs/ajax';
import { catchError, concatMap, delay, from, of, map, Observable } from 'rxjs';
const urls = [
'https://randomuser.me/api/',
'https://randomuser.me/api/',
'https://randomuser.me/api/',
];
const delayAfterCall = 500;
from(urls)
.pipe(
concatMap((url: string) => {
return ajax.getJSON(url).pipe(
map((response) => {
console.log('Done! Received:', response);
return response;
}),
catchError((error) => {
console.error('Error: ', error);
return of(error);
}),
delay(delayAfterCall)
);
})
)
.subscribe((response) => {
console.log('received email:', response.results[0].email);
});
我的答案基于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
});