ファイルの配列をシリアル/シーケンシャルに読み取る次のコードを考えてみます。readFiles
すべてのファイルが順番に読み取られた後にのみ解決される promise を返します。
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);
}));
};
ベストアンサー1
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 など) には、このためのユーティリティ メソッドがあります。
たとえば、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 を使用しない理由は実際にはありません。