web-dev-qa-db-ja.com

約束を次々と(すなわち順番に)解決しますか?

次のコードで、ファイルの配列をシリアル/シーケンシャルに読み取るコードを考えます。 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);
  }));
};
212

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.
});

非同期を使用する理由が実際にはありませんないが今日待ちます。

252

これが、タスクを連続して実行する方法です。

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;
}
62

この質問は古くなっていますが、私たちは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))
52
joelnet

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;
  }
35
Shridhar Gupta

標準Node.js用のシンプルなutilは、次のことを約束します。

function sequence(tasks, fn) {
    return tasks.reduce((promise, task) => promise.then(() => fn(task)), Promise.resolve());
}

UPDATE

items-promise は、NPMパッケージをそのまま使用する準備ができています。

23
Pooya

私はたくさんの逐次的なタスクを実行しなければならず、これらの答えを使って逐次的なタスクを処理する面倒を見てくれる関数を作り上げました。

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 );

誰かが時間を節約できることを願っています...

11
Salketer

これは上記のもう一つの答えのわずかな変化です。ネイティブの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(...)
4
Hai Phan

私が好む解決策:

function processArray(arr, fn) {
    return arr.reduce(
        (p, v) => p.then((a) => fn(v).then(r => a.concat([r]))),
        Promise.resolve([])
    );
}

ここに掲載されている他のものと根本的に違いはありませんが。

  • 関数を系列の項目に適用します
  • 結果の配列に解決
  • 非同期/待機を必要としません(サポートはまだかなり限られており、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)でテスト済み。

4
Molomby

私が理解することができた最も良い解決策はbluebird約束を使うことでした。約束は順番に解決されることを保証するPromise.resolve(files).each(fs.readFileAsync);を実行することができます。

4
Mikael Lepistö

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
});
3
Alexander Mills

Promiseオブジェクトにこの単純なメソッドを作成しました。

PromiseオブジェクトにPromise.sequenceメソッドを作成して追加します。

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を使用してモバイルアプリを作成する場合です。

2
frodeborli

私は@ 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)
  })
2
Gabriel Acosta

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

2
sidanmor

Bergiが気づいたように、私は最善で明確な解決策はBlueBird.eachを使うことだと思います。

const BlueBird = require('bluebird');
BlueBird.each(files, fs.readFileAsync);
2
jtianling

私の答えは 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
});
2
Jason Suttles

なぜ人々がそのような複雑な解決策を提案するのか理解できません。これはもっと単純なロジックです。

Async/Awaitを使用 - ES7をサポートしている場合に最適なソリューション

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);
}
1
Gil Epshtain

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);
1
josemontesp

あなたが欲しいなら、あなたは順次約束をするために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));

それは常に順番に動作します。

1
Victor Palomo

配列プッシュおよびポップ方法は、約束のシーケンスに使用することができます。追加データが必要な場合は、新しい約束をプッシュすることもできます。これがコードです。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);
0
Muthu Kumar

他の誰かが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);
        });    
    });
}
0
Ula

ここにたくさんの答えがありますが、私はこの単純な解決策を見ませんでした:

await array.reduce(
  async (promise, member) => await myLongSequentialPromise(member),
  array[0]
)

証明: https://jsbin.com/nulafus/1/edit?js,console

0
dimiguel

質問の題名「約束を次々に(すなわち順番に)解決する」に基づいて、私たちはOPが逐次呼び出しそれ自体よりも決済に関する約束の逐次処理に関心があることを理解するかもしれませんそれ自体.

この答えは提供されています:

  • 順次呼び出しが応答の順次処理に必要ではないことを実証するため。
  • このページの訪問者に実行可能な代替パターンを公開すること - 1年後にまだ興味がある場合はOPも含めます。
  • oPが彼が同時に電話をかけることを望まないという主張にもかかわらず、それは真実であるかもしれませんが同様にタイトルが意味するように応答の連続した取り扱いに対する欲求に基づく仮定であるかもしれません。

同時通話が本当に望まれていないのであれば、順次通話(その他)を包括的にカバーするBenjamin Gruenbaumの答えを見てください。

ただし、同時呼び出しに続いて応答を順次処理できるようなパターンに興味がある場合は(パフォーマンスを向上させるために)、次にお読みください。

Promise.all(arr.map(fn)).then(fn)(私が何度もやったように)やPromise libの空想の砂糖(特にBluebirdのもの)を使わなければならないと思うのは魅力的です、( this articlearr.map(fn).reduce(fn)パターンはその仕事をするでしょう、それはそれが持つ利点:

  • どんな約束のlibでも動作します - たとえjQueryの前迎合的なバージョンでさえ - .then()だけが使われます。
  • 1行の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()という1つのフラグメントだけがQに固有です。jQueryの場合、readFile()がjQueryの約束を確実に返すようにする必要があります。 A + libsでは、外国の約束は同化されます。

ここで重要なのはリダクションのsequence約束であり、これはreadFile約束の処理を順序付けますが、それらの生成の順序は決めません。

そして、一度それを吸収したら、.map()ステージが実際には必要ではないことに気付いたとき、それは少し気が遠くなるかもしれません!仕事全体、並列呼び出しと正しい順序での逐次処理はreduce()だけで達成でき、さらに以下の点で柔軟性が増すという追加の利点があります。

  • 単純に1行移動することで、パラレル非同期呼び出しからシリアル非同期呼び出しに変換します。開発中に役立つ可能性があります。

これは、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.
    });
};

それが基本的なパターンです。あなたがデータ(例えばファイルやそれらの変換)を呼び出し元にも配信したいのであれば、穏やかなバリアントが必要です。

0
Roamer-1888

あなたのアプローチは悪くありません、しかしそれは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!
};
0
JLRishe