次のコードで、ファイルの配列をシリアル/シーケンシャルに読み取るコードを考えます。 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!
});
};
上記のコードは動作しますが、物事が逐次的に発生するように再帰を行う必要はありません。奇妙なreadSequential
関数を使う必要がないように、このコードを書き換えることができるもっと簡単な方法はありますか?
もともと私はPromise.all
を使おうとしました、しかしそれはすべてのreadFile
呼び出しが同時に起こることを引き起こしました、それは私が望むものではない:
var readFiles = function(files) {
return Promise.all(files.map(function(file) {
return readFile(file);
}));
};
Update 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.
});
非同期を使用する理由が実際にはありませんないが今日待ちます。
これが、タスクを連続して実行する方法です。
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;
}
この質問は古くなっていますが、私たちはES6と機能的なJavaScriptの世界に住んでいるので、どうすれば改善できるか見てみましょう。
約定はすぐに執行されるので、一連の約定を作成することはできません。それらはすべて並行して開始されます。
代わりに、約束を返す一連の関数を作成する必要があります。それから各機能は順次実行され、それは内部で約束を開始します。
これをいくつかの方法で解決できますが、私のお気に入りの方法はreduce
を使用することです。
reduce
をpromiseと組み合わせて使用するのは少し面倒です。そこで、1つのライナーをいくつかの小さな可消化バイトに分割しました。
この関数の本質は、初期値Promise.resolve([])
で始まるreduce
、または空の配列を含むpromiseを使うことです。
この約束はreduce
としてpromise
メソッドに渡されます。これは各約束を順番につなぐための鍵です。次に実行する約束はfunc
で、then
が起動すると結果が連結され、その約束が返され、次のpromise関数でreduce
サイクルが実行されます。
すべての約束が実行されると、返される約束には各約束のすべての結果の配列が含まれます。
ES6の例(1ライナー)
/*
* 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))
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;
}
標準Node.js用のシンプルなutilは、次のことを約束します。
function sequence(tasks, fn) {
return tasks.reduce((promise, task) => promise.then(() => fn(task)), Promise.resolve());
}
UPDATE
items-promise は、NPMパッケージをそのまま使用する準備ができています。
私はたくさんの逐次的なタスクを実行しなければならず、これらの答えを使って逐次的なタスクを処理する面倒を見てくれる関数を作り上げました。
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つの引数を取ります。最初の引数は、これから作業する配列です。 2番目の引数はタスク自体、つまり約束を返す関数です。次のタスクはこの約束が解決されたときにのみ開始されます。 3番目の引数は、すべてのタスクが完了したときに実行されるコールバックです。コールバックが渡されなかった場合、関数はそれが作成した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 );
誰かが時間を節約できることを願っています...
これは上記のもう一つの答えのわずかな変化です。ネイティブのPromiseを使う:
function inSequence(tasks) {
return tasks.reduce((p, task) => p.then(task), Promise.resolve())
}
説明
これらのタスクに[t1, t2, t3]
がある場合、上記はPromise.resolve().then(t1).then(t2).then(t3)
と同じです。それはreduceの振る舞いです。
使い方
最初タスクのリストを作成する必要があります。タスクは引数をとらない関数です。関数に引数を渡す必要がある場合は、bind
などのメソッドを使用してタスクを作成してください。例えば:
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);
妥当な現在のChrome(v59)とNodeJS(v8.1.2)でテスト済み。
私が理解することができた最も良い解決策はbluebird
約束を使うことでした。約束は順番に解決されることを保証するPromise.resolve(files).each(fs.readFileAsync);
を実行することができます。
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 inSeries = function(providers){
const seed = Promise.resolve(null);
return providers.reduce(function(a,b){
return a.then(b);
}, seed);
};
素晴らしく簡単...パフォーマンスのために同じシードを再利用することができるはずです。
reduceを使用する場合は、空の配列または1つの要素のみを持つ配列に対して/ ** /を保護することが重要です。そのため、この方法が最善の策です。
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
});
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.allとPromise.sequenceは同じ方法で呼び出されますが、意味が異なります。
約束を連続して実行することは、通常約束を使用するための非常に良い方法ではありません。通常はPromise.allを使用して、ブラウザでコードをできるだけ速く実行させる方が良いでしょう。ただし、実際の使用例はあります。たとえば、JavaScriptを使用してモバイルアプリを作成する場合です。
私は@ joelnetの答えをとても気に入っていましたが、そのコーディングスタイルは要約するのが少し難しいので、同じ解決策をもっと読みやすく表現する方法を考え出すために2、3日を費やしました。ちょっと違う構文といくつかのコメントで、取ってください。
// 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)
})
PromiseFactoriesリストを取得するこの関数を使うことができます。
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仕様に従って、Promiseが作成されるとすぐに実行されます。それであなたが本当に欲しいものは約束の工場の配列です...
あなたが約束についてもっと知りたいならば、あなたはこのリンクをチェックするべきです: https://pouchdb.com/2015/05/18/we-have-a-problem-with-promises.html
Bergiが気づいたように、私は最善で明確な解決策はBlueBird.eachを使うことだと思います。
const BlueBird = require('bluebird');
BlueBird.each(files, fs.readFileAsync);
私の答えは 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
});
なぜ人々がそのような複雑な解決策を提案するのか理解できません。これはもっと単純なロジックです。
function downloadFile(fileUrl) { ... } // This function return a Promise
async function main()
{
var filesList = [...];
for (var i = 0; i <= filesList.length; i++)
{
await downloadFile(filesList[i]);
}
}
function downloadFile(fileUrl) { ... } // This function return a Promise
function downloadRecursion(filesList, index)
{
if (index < filesList.length)
{
downloadFile(filesList[index]).then(function()
{
index++;
downloadRecursion(filesList, index); // self invocation - recursion!
});
}
else
{
return Promise.resolve();
}
}
function main()
{
var filesList = [...];
downloadFile(filesList, 0);
}
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);
あなたが欲しいなら、あなたは順次約束をするために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));
それは常に順番に動作します。
配列プッシュおよびポップ方法は、約束のシーケンスに使用することができます。追加データが必要な場合は、新しい約束をプッシュすることもできます。これがコードです。ReactInfiniteローダでページのシーケンスをロードするために使用します。
var promises = [Promise.resolve()];
function methodThatReturnsAPromise(page) {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log(`Resolve-${page}! ${new Date()} `);
resolve();
}, 1000);
});
}
function pushPromise(page) {
promises.Push(promises.pop().then(function () {
return methodThatReturnsAPromise(page)
}));
}
pushPromise(1);
pushPromise(2);
pushPromise(3);
他の誰かがCRUD操作を実行するときにPromiseを解決するための厳密な順次解決方法の保証された方法を必要とするならば、あなたは基礎として以下のコードを使うこともできます。
各関数を呼び出す前に 'return'を追加し、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);
});
});
}
ここにたくさんの答えがありますが、私はこの単純な解決策を見ませんでした:
await array.reduce(
async (promise, member) => await myLongSequentialPromise(member),
array[0]
)
質問の題名「約束を次々に(すなわち順番に)解決する」に基づいて、私たちはOPが逐次呼び出しそれ自体よりも決済に関する約束の逐次処理に関心があることを理解するかもしれませんそれ自体.
この答えは提供されています:
同時通話が本当に望まれていないのであれば、順次通話(その他)を包括的にカバーするBenjamin Gruenbaumの答えを見てください。
ただし、同時呼び出しに続いて応答を順次処理できるようなパターンに興味がある場合は(パフォーマンスを向上させるために)、次にお読みください。
Promise.all(arr.map(fn)).then(fn)
(私が何度もやったように)やPromise libの空想の砂糖(特にBluebirdのもの)を使わなければならないと思うのは魅力的です、( this article へarr.map(fn).reduce(fn)
パターンはその仕事をするでしょう、それはそれが持つ利点:
.then()
だけが使われます。これは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()
という1つのフラグメントだけがQに固有です。jQueryの場合、readFile()がjQueryの約束を確実に返すようにする必要があります。 A + libsでは、外国の約束は同化されます。
ここで重要なのはリダクションのsequence
約束であり、これは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.
});
};
それが基本的なパターンです。あなたがデータ(例えばファイルやそれらの変換)を呼び出し元にも配信したいのであれば、穏やかなバリアントが必要です。
あなたのアプローチは悪くありません、しかしそれは2つの問題を抱えています:それはエラーを飲み込みそしてそれは明示的な約束構築アンチパターンを採用します。
同じ一般的な戦略を採用しながら、これらの問題の両方を解決し、コードをよりクリーンにすることができます。
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!
};