約束の配列を順番に実行するにはどうすればよいですか?


81

順番に実行する必要のある一連のPromiseがあります。

var promises = [promise1, promise2, ..., promiseN];

RSVP.allを呼び出すと、それらが並行して実行されます。

RSVP.all(promises).then(...); 

しかし、どうすればそれらを順番に実行できますか?

このように手動で積み重ねることができます

RSVP.resolve()
    .then(promise1)
    .then(promise2)
    ...
    .then(promiseN)
    .then(...);

ただし、問題は、Promiseの数が変化し、Promiseの配列が動的に構築されることです。


鉱山の他の回答とdownvotesから、より多くの人々が読む必要があると思われるRSVPのREADMEそれは「あなたが最初のハンドラからの約束を返すときは本当に素晴らしい部分が来る」について説明します。あなたがこれをしていなければ、あなたは約束の表現力を本当に逃しています。
マイケルジョンストン

同様の質問ですが、フレームワーク固有ではありません:stackoverflow.com/q/24586110/245966
jakub.g 2017

回答:


136

それらがすでに配列にある場合、それらはすでに実行されています。あなたが約束を持っているなら、それはすでに実行されています。これはpromiseの問題ではありません(Taskつまり、.Start()メソッドに関してはC#とは異なります)。.all何も実行せず、promiseを返すだけです。

関数を返すpromiseの配列がある場合:

var tasks = [fn1, fn2, fn3...];

tasks.reduce(function(cur, next) {
    return cur.then(next);
}, RSVP.resolve()).then(function() {
    //all executed
});

または値:

var idsToDelete = [1,2,3];

idsToDelete.reduce(function(cur, next) {
    return cur.then(function() {
        return http.post("/delete.php?id=" + next);
    });
}, RSVP.resolve()).then(function() {
    //all executed
});

3
これは、引数を必要としない同種の約束のツリーを構築するための優れた方法です。これは、next_promiseポインターを使用してツリーを自分で構築するのとまったく同じです。これは、promiseのセットが引数などに関して均一でない場合に実行する必要があります。reduce関数が現在のポインターを実行しているだけです。 -あなたのためのリーフビット。また、いくつかのことが同時に発生する可能性がある場合は、自分自身のツリーを構築することもできます。約束のツリーでは、ブランチはシーケンスであり、リーフは同時です。
マイケルジョンストン

ご回答ありがとうございます。あなたは、約束を作成することはそれが実行されていることをすでに意味しているので、私の質問が正しく形成されなかったことは正しいです。私は約束なしに自分の問題を別の方法で解決することになった。
jaaksarv 2013

1
@SSHThisさて、まず第一に、ワット。次に、前の応答がに渡さ.thenれます。この例では、無視されます...
Esailija 2015年

3
これらの約束のいずれかが失敗した場合、エラーが拒否されることはなく、約束が解決されることもありません...
Maxwelll 2016年

5
それらがすでに配列にある場合、それらはすでに実行されています。-このフレーズは太字+大きなフォントである必要があります。理解することが重要です。
ducin 2017年

22

ECMAScript 2017非同期関数では、次のように実行されます。

async function executeSequentially() {
    const tasks = [fn1, fn2, fn3]

    for (const fn of tasks) {
        await fn()
    }
}

BabelJSを使用して非同期関数を使用できるようになりました


これは、今(2020年)までのデフォルトのアプローチになるはずです。初めてのユーザーにとっては、ここで2つのことに注意することが重要かもしれません。1。約束が存在すると、それはすでに実行されています。それは本当に重要ですので、2はそれをfn1, fn2, fn3ここで例えば機能している() => yourFunctionReturningAPromise()だけではなくてはyourFunctionReturningAPromise()。これは、await fn()代わりに必要な理由でもありawait fnます。詳細については、公式ドキュメントをご覧ください。申し訳ありませんがコメントとして投稿が、編集キューのフル:)です
ezmegyは、

7

2017年のES7ウェイ。

  <script>
  var funcs = [
    _ => new Promise(resolve => setTimeout(_ => resolve("1"), 1000)),
    _ => new Promise(resolve => setTimeout(_ => resolve("2"), 1000)),
    _ => new Promise(resolve => setTimeout(_ => resolve("3"), 1000)),
    _ => new Promise(resolve => setTimeout(_ => resolve("4"), 1000)),
    _ => new Promise(resolve => setTimeout(_ => resolve("5"), 1000)),
    _ => new Promise(resolve => setTimeout(_ => resolve("6"), 1000)),
    _ => new Promise(resolve => setTimeout(_ => resolve("7"), 1000))
  ];
  async function runPromisesInSequence(promises) {
    for (let promise of promises) {
      console.log(await promise());
    }
  }
  </script>
  <button onClick="runPromisesInSequence(funcs)">Do the thing</button>

これにより、指定された関数が並列ではなく順次(1つずつ)実行されます。パラメータpromisesは関数の配列であり、を返しPromiseます。

上記のコードを使用したプランカーの例:http://plnkr.co/edit/UP0rhD?p = Preview


4

私がより説明しようとする答えの2番目の試み:

まず、RSVP READMEからのいくつかの必要な背景:

本当に素晴らしい部分は、最初のハンドラーからpromiseを返すときに発生します...これにより、ネストされたコールバックをフラット化できます。これは、非同期コードが多いプログラムでの「右方向へのドリフト」を防ぐPromiseの主な機能です。

これはまさに、then前に終了するはずのプロミスから後のプロミスを返すことによって、プロミスをシーケンシャルにする方法です。

このような一連のpromiseをツリーとして考えると役立ちます。ここで、ブランチは順次プロセスを表し、リーフは並行プロセスを表します。

このような約束のツリーを構築するプロセスは、他の種類のツリーを構築するという非常に一般的なタスクに類似しています。ツリー内の現在ブランチを追加している場所へのポインターまたは参照を維持し、繰り返し追加します。

@Esailijaが彼の回答で指摘したように、引数を取らないpromiseを返す関数の配列がある場合はreduce、ツリーをきちんと構築するために使用できます。自分でreduceを実装したことがある場合は、@ Esailijaの回答の舞台裏でreduceが行っていることは、現在のpromise(cur)への参照を維持し、各promiseに次のpromiseを返すことであることを理解できますthen

同種の(それらが取る/返す引数に関して)promise戻り関数の素晴らしい配列がない場合、または単純な線形シーケンスよりも複雑な構造が必要な場合は、次のようにしてpromiseのツリーを自分で構築できます。新しいPromiseを追加するPromiseツリー内の位置への参照:

var root_promise = current_promise = Ember.Deferred.create(); 
// you can also just use your first real promise as the root; the advantage of  
// using an empty one is in the case where the process of BUILDING your tree of 
// promises is also asynchronous and you need to make sure it is built first 
// before starting it

current_promise = current_promise.then(function(){
  return // ...something that returns a promise...;
});

current_promise = current_promise.then(function(){
  return // ...something that returns a promise...;
});

// etc.

root_promise.resolve();

RSVP.allを使用して、promiseの「ブランチ」に複数の「リーフ」を追加することにより、並行プロセスと順次プロセスの組み合わせを構築できます。私のあまりにも複雑な答えは、その例を示しています。

また、Ember.run.scheduleOnce( 'afterRender')を使用して、次のプロミスが実行される前に、あるプロミスで行われた何かがレンダリングされるようにすることもできます。


3
これははるかに優れていますが、あなたはまだ話題から外れているように感じます。これは約束に関する多くの回答に共通しており、人々は質問を読むのに時間がかからないようです。代わりに、個人的に理解している約束のいくつかの側面についてコメントするだけです。元の質問は並列実行を含まず、少しでもありません。それは、単純に連鎖することthenが望ましいことを明確に示しています。質問された質問への回答を隠している多くの追加情報を提供しました。
David McMullin

@DavidMcMullin "....そして、それを介して単純に連鎖することが望ましいことを明確に示しています..."しかし、実際には、約束のシーケンスは動的に構築されていると彼は述べています。したがって、この場合はツリーの「線形シーケンス」の単純なサブセットであっても、ツリーの構築方法を理解する必要があります。チェーン内の最後のpromiseへの参照を維持し、それに新しいpromiseを追加して、それを構築する必要があります。
マイケルジョンストン

OPが「promiseの数は変化し、promiseの配列は動的に構築される」と言ったとき、彼/彼が意味したのは、配列のサイズが事前に決定されておらず、したがって、単純なものを使用できないということでした。Promise.resolve().then(...).then(...)...、アレイが成長している間ではありません promiseの実行て。もちろん、今はすべて議論の余地があります。
JLRishe 2015

4

さらに別のアプローチは、プロトタイプでグローバルシーケンス関数を定義することPromiseです。

Promise.prototype.sequence = async (promiseFns) => {
  for (let promiseFn of promiseFns) {
    await promiseFn();
  }
}

そうすれば、どこでも使用できます。 Promise.all()

const timeout = async ms => new Promise(resolve =>
  setTimeout(() => {
    console.log("done", ms);
    resolve();
  }, ms)
);

// Executed one after the other
await Promise.sequence([() => timeout(1000), () => timeout(500)]);
// done: 1000
// done: 500

// Executed in parallel
await Promise.all([timeout(1000), timeout(500)]);
// done: 500
// done: 1000

免責事項:プロトタイプの編集には注意してください!


2

それを解決するにはすべてが必要ですfor:)

var promises = [a,b,c];
var chain;

for(let i in promises){
  if(chain) chain = chain.then(promises[i]);
  if(!chain) chain = promises[i]();
}

function a(){
  return new Promise((resolve)=>{
    setTimeout(function(){
      console.log('resolve A');
      resolve();
    },1000);
  });
}
function b(){
  return new Promise((resolve)=>{
    setTimeout(function(){
      console.log('resolve B');
      resolve();
    },500);
  });
}
function c(){
  return new Promise((resolve)=>{
    setTimeout(function(){
      console.log('resolve C');
      resolve();
    },100);
  });
}

なぜif(!chain) chain = promises[i]();持って()終わり?チェーンが空の場合(反復0)、生のpromiseが必要なだけで、ループは後続の各promiseをチェーンに注入できると思い.then()ます。したがって、これはそうではありませんif(!chain) chain = promises[i];か?おそらく私はここで何かを理解していません。
15:49のハーフ

ああ-あなたa,b,cは確かにPromisesを返す関数であり、Promisesではありません。したがって、上記は理にかなっています。しかし、このように約束を包むことにはどのような有用性がありますか?
ハーフ

2

同様の問題があり、関数を1つずつ順番に実行する再帰関数を作成しました。

var tasks = [fn1, fn2, fn3];

var executeSequentially = function(tasks) {
  if (tasks && tasks.length > 0) {
    var task = tasks.shift();

    return task().then(function() {
      return executeSequentially(tasks);
    });
  }

  return Promise.resolve();  
};

これらの関数から出力を収集する必要がある場合:

var tasks = [fn1, fn2, fn3];

var executeSequentially = function(tasks) {
  if (tasks && tasks.length > 0) {
    var task = tasks.shift();

    return task().then(function(output) {
      return executeSequentially(tasks).then(function(outputs) {
        outputs.push(output);

        return Promise.resolve(outputs);  
      });
    });
  }

  return Promise.resolve([]);
};

0
export type PromiseFn = () => Promise<any>;

export class PromiseSequence {
  private fns: PromiseFn[] = [];

  push(fn: PromiseFn) {
    this.fns.push(fn)
  }

  async run() {
    for (const fn of this.fns) {
      await fn();
    }
  }
}

その後

const seq = new PromiseSequence();
seq.push(() => Promise.resolve(1));
seq.push(() => Promise.resolve(2));
seq.run();

約束が返すものを別のプライベート変数に保存し、それをコールバックに渡すことも可能です


-1

私が求めていたのは本質的にmapSeriesであり、たまたま一連の値をマッピングして保存していたので、結果が必要です。

だから、私が得た限りでは、将来同様のものを探している他の人を助けるためにここにあります。

(コンテキストはEmberアプリであることに注意してください)。

App = Ember.Application.create();

App.Router.map(function () {
    // put your routes here
});

App.IndexRoute = Ember.Route.extend({
    model: function () {
            var block1 = Em.Object.create({save: function() {
                return Em.RSVP.resolve("hello");
            }});
    var block2 = Em.Object.create({save: function() {
            return Em.RSVP.resolve("this");
        }});
    var block3 = Em.Object.create({save: function() {
        return Em.RSVP.resolve("is in sequence");
    }});

    var values = [block1, block2, block3];

    // want to sequentially iterate over each, use reduce, build an array of results similarly to map...

    var x = values.reduce(function(memo, current) {
        var last;
        if(memo.length < 1) {
            last = current.save();
        } else {
            last = memo[memo.length - 1];
        }
        return memo.concat(last.then(function(results) {
            return current.save();
        }));
    }, []);

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