約束を次々に解決しますか(つまり、順番に)?


269

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

2
以前の非同期操作が完了するのを待つ必要があるものはすべて、コールバックで実行する必要があります。promiseを使用してもそれは変わりません。したがって、再帰が必要です。
Barmar 14

1
参考までに、スタックフレームの構築がないため、これは技術的に再帰ではありません。以前はreadFileSequential()次のものが呼び出される前に、すでに(それは非同期だから、それは元の関数の呼び出しが既に返された後も長く完了)戻ってきました。
jfriend00 14

1
@ jfriend00再帰にはスタックフレームの累積は必要ありません。自己参照のみです。ただし、これは単なる専門知識です。
Benjamin Gruenbaum 2014

3
@BenjaminGruenbaum-私のポイントは、次のイテレーションを開始するために関数自体を呼び出すことには何の問題もないということです。これにはマイナス面がありません。実際、非同期操作をシーケンス処理する効率的な方法です。したがって、再帰のように見えるものを回避する理由はありません。非効率的ないくつかの問題に対する再帰的な解決策があります-これはそれらの1つではありません。
jfriend00 2014

1
ねえ、JavaScriptルームでのディスカッションとリクエストに応じて、この回答を編集して、他の人に正規のものとして指摘できるようにしました。同意できない場合はお知らせください。復元し、別のファイルを開きます。
Benjamin Gruenbaum

回答:


337

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

非同期を使用しない理由は本当にありませんが、今日は待ってください。


2
@EmreTapcı、いや。アロー関数の「=>」はすでに戻ることを意味します。
最大

TypeScriptを使用する場合、「for in」ループソリューションが最適だと思います。たとえば、再帰的な約束を減らします。最初の呼び出しの戻り値の型はPromise <void>で、2番目の呼び出しはPromise <Promise <void >>のようになります
Artur Tagisow

@ArturTagisow TypeScript(少なくとも新しいバージョン)には再帰型があり、ここで型を正しく解決する必要があります。Promiseは「再帰的に同化する」ため、Promise <Promise <T >>のようなものはありません。Promise.resolve(Promise.resolve(15))と同じですPromise.resolve(15)
ベンジャミングレンバウム


72

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

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

8
また、正確なタスク数がわからない場合はどうでしょうか。
2016

1
そして、実行時だけで、タスクの数がわかっている場合はどうでしょうか?
joeytwiddle

10
「promiseの配列を操作する必要はまったくありません。promise仕様に従って、promiseが作成されるとすぐに実行が開始されます。したがって、本当に望んでいるのは、promiseファクトリーの配列です」高度な間違い#3を参照ここ:pouchdb.com/2015/05/18/we-have-a-problem-with-promises.html
edelans

5
ラインノイズを減らすことにresult = result.then(task);
興味

1
@DanielBuckmasterはい、ただし、task()が値を返すと、次の呼び出しに渡されるため、注意してください。タスクにオプションの引数がある場合、これは副作用を引き起こす可能性があります。現在のコードは結果を飲み込み、引数なしで次のタスクを明示的に呼び出します。
JHH 2018

63

この質問は古いですが、私たちはES6と機能的なJavaScriptの世界に住んでいるので、どのように改善できるか見てみましょう。

promiseはすぐに実行されるため、promiseの配列を作成するだけではなく、すべて並行して実行されます。

代わりに、promiseを返す関数の配列を作成する必要があります。次に、各関数が順次実行され、内部でpromiseが開始されます。

これはいくつかの方法で解決できますが、私のお気に入りの方法は reduceです。

それを使用して少しトリッキーになります reduceと組み合わせてので、1つのライナーをいくつかの小さな消化可能なバイトに分解しました。

この関数の本質はreduce、初期値としてPromise.resolve([])、または空の配列を含むpromiseです。

その後、このpromiseはreduceとしてメソッドに渡されpromiseます。これは、各プロミスを順番にチェーン化するための鍵です。次に実行するプロミスは、起動functhenに結果が連結され、そのプロミスが返されて、reduce次の約束機能をサイクル。

すべてのpromiseが実行されると、返されたpromiseには、各promiseのすべての結果の配列が含まれます。

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

1
非常に良い、ありがとう、Array.prototype.concat.bind(result)私が欠けていた部分は、手動で結果にプッシュする必要がありましたが、
うまくいき

私たちはすべて最新のJSを扱っているのでconsole.log.bind(console)、あなたの最後の例のステートメントは通常不要になっていると思います。最近は合格できますconsole.log。例えば。serial(funcs).then(console.log)。現在のnodejsとChromeでテストされています。
Molomby 2017

これは私の頭を包むのに少し大変でしたが、削減は本質的にこれを正しく行っていますか? Promise.resolve([]).then((x) => { const data = mockApi('/data/1'); return Promise.resolve(x.concat(data)) }).then((x) => { const data = mockApi('/data/2'); return Promise.resolve(x.concat(data)); });
danecando

@danecando、はい、これは正しいようです。戻り値にPromise.resolveをドロップすることもできます。返された値は、Promise.rejectを呼び出さない限り自動的に解決されます。
joelnet

@ joelnet、danecandoのコメントへの応答として、私は次の式で削減が何をより正確に表現するべきであると思います、あなたは同意しますか?Promise.resolve([]).then(x => someApiCall('url1').then(r => x.concat(r))).then(x => someApiCall('url2').then(r => x.concat(r)))その他
bufferoverflow76 '30 / 08/30

37

これを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(file => {

    // Chain one computation onto the sequence
    sequence = 
      sequence
        .then(() => performComputation(file))
        .then(result => doSomething(result)); 
        // Resolves for each file, one at a time.

  })

  // This will resolve after the entire chain is resolved
  return sequence;
}

1
アンダースコアを使用しているようです。files.forEachファイルが配列の場合は簡略化できます。
グスタボロドリゲス

2
まあ...それはES5です。ES6の方法は次のようになりますfor (file of files) {...}
グスタボロドリゲス

1
あなたはあなたがPromise.resolve()実際の人生ですでに解決された約束を作るために使うべきではないと言います。何故なの?Promise.resolve()よりもきれいなようですnew Promise(success => success())
canac

8
@canac申し訳ありませんが、それは言葉遊びの冗談でした( "empty promises ..")。間違いなくPromise.resolve();あなたのコードで使用してください。
Shridhar Gupta 2017

1
従うのが簡単な素晴らしい解決策。私は最後にそう解決するには、関数の中で地雷を同封していない代わりに置くことでreturn sequence;、私は入れsequence.then(() => { do stuff });
ジョーCoyleの

25

標準のNode.js promiseのシンプルなユーティリティ:

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

更新

items-promiseは、すぐに使えるNPMパッケージです。


6
これについて詳しく説明したいのですが。
Tyguy7

この回答のバリエーションを以下の説明とともに提供しました。ありがとう
Sarsaparilla 2017

これは、非同期/待機にアクセスできないノード7以前の環境で私が行うこととまったく同じです。素敵できれい。
JHH 2018

11

多くの順次タスクを実行する必要があり、これらの回答を使用して、任意の順次タスクの処理を処理する関数を偽造しました...

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番目の引数はタスク自体であり、promiseを返す関数です。次のタスクは、このpromiseが解決されたときにのみ開始されます。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 );

誰かの時間を節約してくれることを願っています...


信じられないほどの解決策、それは私が約1週間の苦闘の中で見つけた最高のものです...それは非常によく説明されており、論理的な内部名、良い例(より良いかもしれません)を持っていますコールバックを設定するオプションが含まれています。単にいいです!(名前をわかりやすい名前に変更しただけです。)...他の人への推奨... 'objects_array'として'Object.keys(myObject)' を使用してオブジェクトを反復できます
DavidTaubmann

コメントありがとうございます!私はその名前も使用していませんが、ここではよりわかりやすく/簡単にしたいと思いました。
Salketer 2017

5

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


1
さらに良い:Promise.each(filtes, fs.readFileAsync)。ところで、あなたがする必要はありませ.bind(fs)んか?
ベルギ

配列とシーケンスの違いを理解している人はいません。後者は無制限/動的サイズを意味します。
vitaly-t 2015

JavaScriptの配列は、Cスタイル言語の固定サイズの配列とは関係がないことに注意してください。これらは単なる数値キー管理が組み込まれたオブジェクトであり、規定のサイズや制限はありません(特にを使用する場合new Array(int)はそうではありません。lengthキーと値のペアが事前に設定されているため、長さベースの反復中に使用されるインデックスの数に影響します。ゼロです。実際の配列のインデックス付けまたはインデックス境界への影響)
Mike 'Pomax' Kamermans

4

これは、上記の別の回答のわずかなバリエーションです。ネイティブプロミスの使用:

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

私の好ましい解決策:

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)でテストされています。


3

を使用しArray.prototype.reduce、Promiseを関数でラップすることを忘れないでください。そうしないと、Promiseはすでに実行されます。

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

2

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

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.allとPromise.sequenceは同じ方法で呼び出されますが、セマンティクスは異なります。

注意

通常、promiseを順次実行することは、promiseを使用するための非常に良い方法ではありません。通常はPromise.allを使用し、ブラウザにコードをできるだけ速く実行させることをお勧めします。ただし、実際の使用例があります。たとえば、JavaScriptを使用してモバイルアプリを作成する場合などです。


いいえ、あなたは比較することはできませんPromise.allし、あなたPromise.sequence。1つは反復可能なpromiseを受け取り、もう1つはpromiseを返す一連の関数を受け取ります。
Bergi、2015


イテレータがかかることを知りませんでした。しかし、それを書き直すのに十分簡単なはずです。これがプロミスコンストラクタのアンチパターンである理由を詳しく説明してください。私はあなたの投稿をここで読みました:stackoverflow.com/a/25569299/1667011
frodeborli

@Bergiイテレータをサポートするようにコードを更新しました。これがアンチパターンであることはまだわかりません。アンチパターンは通常、コーディングの誤りを回避するためのガイドラインと見なされます。これらのガイドラインに違反する(ライブラリ)関数を作成することは完全に有効です。
frodeborli 2015

ええ、あなたがそれをライブラリー関数と考えればそれは問題ありませんが、それでもこの場合reduceベンジャミンの答えのようなものはもっとずっと単純です。
Bergi

2

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仕様に従って、Promiseが作成されるとすぐに、Promiseが実行を開始します。だからあなたが本当に欲しいのは一連の約束の工場です...

Promiseについて詳しく知りたい場合は、次のリンクを確認してください:https : //pouchdb.com/2015/05/18/we-have-a-problem-with-promises.html


2

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

私は@joelnetの答えが本当に好きでしたが、私にとっては、そのコーディングスタイルは少し難しいので、同じソリューションをより読みやすい方法で表現する方法を理解するために数日を費やしました。別の構文といくつかのコメントを付けてください。

// 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

Bergiが気づいたように、最善かつ明確な解決策はBlueBird.eachを使用することだと思います。以下のコード:

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

2

まず、プロミスは作成時に実行されることを理解する必要があります。
たとえば、コードがある場合:

["a","b","c"].map(x => returnsPromise(x))

次のように変更する必要があります。

["a","b","c"].map(x => () => returnsPromise(x))

次に、promiseを順番にチェーンする必要があります。

["a", "b", "c"].map(x => () => returnsPromise(x))
    .reduce(
        (before, after) => before.then(_ => after()),
        Promise.resolve()
    )

実行するとafter()、約束がその時が来たときにのみ作成(および実行)されます。


1

次のコードを使用して、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

必要な場合は、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

最新のESの使用:

const series = async (tasks) => {
  const results = [];

  for (const task of tasks) {
    const result = await task;

    results.push(result);
  }

  return results;
};

//...

const readFiles = await series(files.map(readFile));

1

非同期/待機(ES7のサポートがある場合)

function downloadFile(fileUrl) { ... } // This function return a Promise

async function main()
{
  var filesList = [...];

  for (const file of filesList) {
    await downloadFile(file);
  }
}

forループを使用する必要があります。forEach async / awaitがforEachループでの実行に問題があるためではます)

非同期/待機なし(Promiseを使用)

function downloadFile(fileUrl) { ... } // This function return a Promise

function downloadRecursion(filesList, index)
{
  index = index || 0;
  if (index < filesList.length)
  {
    downloadFile(filesList[index]).then(function()
    {
      index++;
      downloadRecursion(filesList, index); // self invocation - recursion!
    });
  }
  else
  {
    return Promise.resolve();
  }
}

function main()
{
  var filesList = [...];
  downloadRecursion(filesList);
}

2
forEach内で待つことはお勧めしません。
MarceloAgimóvel19年

MarceloAgimóvel@ -私はしていない仕事に解に更新されましたforEach(によると、この
ギルEpshtain

0

「次々に約束を(つまり、順番に)解決しますか?」という質問のタイトルに基づいて、OPは、順次呼び出し自体よりも、決済に関する約束の順次処理に関心があることを理解できます。

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

  • 応答の順次処理に順次呼び出しは必要ないことを示すため。
  • 実行可能な代替パターンをこのページのビジターに公開します。1年経ってもOPに関心がある場合はOPを含めます。
  • OPが同時に電話をかけたくないという主張にも関わらず、これは本当の可能性がありますが、同様に、タイトルが示すように応答を順次処理したいという要望に基づく仮定である可能性があります。

同時通話が本当に望まれない場合は、順次通話などを包括的にカバーするベンジャミン・グルーエンバウムの回答を参照してください。

ただし、同時呼び出しとそれに続く応答の順次処理を許可するパターンに(パフォーマンス向上のために)興味がある場合は、以下をお読みください。

あなたがPromise.all(arr.map(fn)).then(fn)(私が何度も行ったように)またはPromise libのファンシーシュガー(特にBluebirdのもの)を使用する必要があると思うのは魅力的ですが、(この記事のおかげでarr.map(fn).reduce(fn)パターンはうまく機能し、その利点があります。

  • すべてのpromise 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()Qに固有のフラグメントは1つだけです。jQueryの場合、readFile()がjQuery promiseを返すことを確認する必要があります。A +ライブラリーを使用すると、外国の約束が同化されます。

ここで重要なのは、還元の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.
    });
};

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


OPの意図に反する質問に答えることは良い考えではないと思います…
Bergi

1
これsequence.then(() => filePromise)はアンチパターンです-できる限り早くエラーを伝播しません(そしてエラーunhandledRejectionをサポートするライブラリで作成します)。Q.all([sequence, filePromise])またはを使用する必要があり$.when(sequence, filePromise)ます。確かに、この動作、エラーを無視またはスキップすることを目的とする場合に必要な動作になる可能性がありますが、少なくともこれをデメリットとして言及する必要があります。
Bergi

@ベルジ、私はOPが介入し、これが本当に彼の意図に反するかどうかについて判断を下すことを望んでいます。そうでない場合は、私が推測した答えを削除します。その間、自分の立場を正当化したことを願っています。まともなフィードバックを提供するのに十分に真剣に受け止めていただきありがとうございます。アンチパターンについて詳しく説明してもらえますか、それともリファレンスを提供してください。基本パターンを見つけた記事も同様ですか?
Roamer-1888

1
はい、彼のコードの3番目のバージョン(「並列と順次の両方」)にも同じ問題があります。「アンチパターン」は高度なエラー処理を必要とし、非同期にハンドラーをアタッチする傾向があり、これによりunhandledRejectionイベントが発生します。Bluebird sequence.return(filePromise)では、動作は同じですが拒否を適切に処理することにより、これを回避できます。参考文献はわかりませんが、思いついただけです。「(アンチ)パターン」にはまだ名前が付いていないと思います。
ベルギ

1
@Bergi、あなたは私が見えないものをはっきりと見ることができます:(この新しいアンチパターンをどこかに文書化する必要があるのでしょうか?
Roamer-1888

0

あなたのアプローチは悪くありませんが、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

他の誰かがCRUD操作を実行するときに約束を厳密に順次解決する保証された方法が必要な場合は、次のコードを基礎として使用することもできます。

各関数を呼び出す前に '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

配列のpushおよびpopメソッドは、promiseのシーケンスに使用できます。追加のデータが必要な場合は、新しいプロミスをプッシュすることもできます。これはコードです。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

ほとんどの回答には、すべてのpromiseの結果が個別に含まれていないため、誰かがこの特定の動作を探している場合、これは再帰を使用した可能な解決策です。

それは次のスタイルに従いますPromise.all

  • .then()コールバックで結果の配列を返します。

  • 一部のpromiseが失敗した場合、その.catch()コールバックですぐに返されます。

const promiseEach = (arrayOfTasks) => {
  let results = []
  return new Promise((resolve, reject) => {
    const resolveNext = (arrayOfTasks) => {
      // If all tasks are already resolved, return the final array of results
      if (arrayOfTasks.length === 0) return resolve(results)

      // Extract first promise and solve it
      const first = arrayOfTasks.shift()

      first().then((res) => {
        results.push(res)
        resolveNext(arrayOfTasks)
      }).catch((err) => {
        reject(err)
      })
    }
    resolveNext(arrayOfTasks)
  })
}

// Lets try it 😎

const promise = (time, shouldThrowError) => new Promise((resolve, reject) => {
  const timeInMs = time * 1000
  setTimeout(()=>{
    console.log(`Waited ${time} secs`)
    if (shouldThrowError) reject(new Error('Promise failed'))
    resolve(time)
  }, timeInMs)
})

const tasks = [() => promise(1), () => promise(2)]

promiseEach(tasks)
  .then((res) => {
    console.log(res) // [1, 2]
  })
  // Oops some promise failed
  .catch((error) => {
    console.log(error)
  })

tasks配列宣言についての注意

この場合、次のように表記することはできませんPromise.all

const tasks = [promise(1), promise(2)]

そして、私たちは使用する必要があります:

const tasks = [() => promise(1), () => promise(2)]

その理由は、JavaScriptは宣言された直後にpromiseの実行を開始するためです。私たちはのようなメソッドを使用している場合Promise.all、それはそれらのすべての状態はちょうどチェックをされていることfulfilledrejected、しかし・実行そのものを開始しdoesntの。使用() => promise()することは、それが呼び出されるまで実行を停止します。


0
(function() {
  function sleep(ms) {
    return new Promise(function(resolve) {
      setTimeout(function() {
        return resolve();
      }, ms);
    });
  }

  function serial(arr, index, results) {
    if (index == arr.length) {
      return Promise.resolve(results);
    }
    return new Promise(function(resolve, reject) {
      if (!index) {
        index = 0;
        results = [];
      }
      return arr[index]()
        .then(function(d) {
          return resolve(d);
        })
        .catch(function(err) {
          return reject(err);
        });
    })
      .then(function(result) {
        console.log("here");
        results.push(result);
        return serial(arr, index + 1, results);
      })
      .catch(function(err) {
        throw err;
      });
  }

  const a = [5000, 5000, 5000];

  serial(a.map(x => () => sleep(x)));
})();

ここで重要なのは、sleep関数を呼び出す方法です。それ自体がpromiseの配列ではなくpromiseを返す関数の配列を渡す必要があります。


-1

これは、より一般的な方法でpromiseのシーケンスを処理する方法を拡張し、spex.sequence実装に基づいて動的/無限シーケンスをサポートします。

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

このソリューションは、あらゆるサイズのシーケンスで機能するだけでなく、データスロットリングや負荷分散を簡単に追加できます

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.