多くの約束を返し、他のことをする前にそれらすべてを待つ方法


91

非同期で処理を行うメソッドを呼び出すループがあります。このループはメソッドを何度も呼び出すことができます。このループの後に、すべての非同期処理が完了したときにのみ実行する必要がある別のループがあります。

だからこれは私が欲しいものを示しています:

for (i = 0; i < 5; i++) {
    doSomeAsyncStuff();    
}

for (i = 0; i < 5; i++) {
    doSomeStuffOnlyWhenTheAsyncStuffIsFinish();    
}

私は約束にあまり精通していないので、誰かが私がこれを達成するのを手伝ってくれるでしょうか?

これが私のdoSomeAsyncStuff()振る舞いです:

function doSomeAsyncStuff() {
    var editor = generateCKEditor();
    editor.on('instanceReady', function(evt) {
        doSomeStuff();
        // There should be the resolve() of the promises I think.
    })
}

多分私はこのようなことをしなければなりません:

function doSomeAsyncStuff() {
    var editor = generateCKEditor();
    return new Promise(function(resolve,refuse) {
        editor.on('instanceReady', function(evt) {
            doSomeStuff();
            resolve(true);
        });
    });
}

しかし、構文はわかりません。


非同期呼び出しを制御していますか?彼らはすでに約束を返しますか、それともあなたは彼らに約束を返させることができますか?
TJ Crowder 2015

シーケンスは正確には何ですか?以前のすべての非同期関数が終了た後、他の関数を呼び出す必要がありますか?または、各非同期が終了した後に関数を呼び出す必要がありますか?
Sosdoc 2015

今のところ、最初の関数はpromiseを返しません。私が実装しなければならないこと。メッセージを編集して、関数のワークフローの詳細を追加したいと思います。そして、はい、2番目のループの実行を開始する前に、最初のループのすべてのものを終了する必要があります。
ガンビン2015

1
あなたの編集について:「たぶん私はそのようなことをしなければならない」うん、それと非常によく似ているが、sの終わりに何もないPromise
TJ Crowder 2015

回答:


169

そのためにPromise.allspecMDN)を使用できます。これは、多数の個別のPromiseを受け入れ、指定したすべてのPromiseが解決されたときに解決されるか、いずれかが拒否されたときに拒否される単一のPromiseを返します。

したがって、doSomeAsyncStuff返品を約束すると、次のようになります。

    const promises = [];
//  ^^^^^−−−−−−−−−−−−−−−−−−−−−−−−−−− use `const` or `let`, not `var`
    
    for (let i = 0; i < 5; i++) {
//       ^^^−−−−−−−−−−−−−−−−−−−−−−−− added missing declaration
        promises.push(doSomeAsyncStuff());
    }
    
    Promise.all(promises)
        .then(() => {
            for (let i = 0; i < 5; i++) {
//               ^^^−−−−−−−−−−−−−−−− added missing declaration
                doSomeStuffOnlyWhenTheAsyncStuffIsFinish();    
            }
        })
        .catch((e) => {
            // handle errors here
        });

MDNには、約束に関する記事がここにあります。また、私の本「JavaScript:The New Toys」の第8章で、プロミスについて詳しく説明しています。興味がある場合は、プロフィールのリンクを参照してください。

次に例を示します。

 function doSomethingAsync(value) {
     return new Promise((resolve) => {
         setTimeout(() => {
             console.log("Resolving " + value);
             resolve(value);
         }, Math.floor(Math.random() * 1000));
     });
   }
   
   function test() {
       const promises = [];
       
       for (let i = 0; i < 5; ++i) {
           promises.push(doSomethingAsync(i));
       }
       
       Promise.all(promises)
           .then((results) => {
               console.log("All done", results);
           })
           .catch((e) => {
               // Handle errors here
           });
   }
   
   test();

サンプル出力(のため、Math.random最初に終了するものは異なる場合があります):

解決3
解決2
解決1
解決4
0を解決する
すべて完了[0,1,2,3,4]

わかりました、今これを試してみて、数分でフィードバックが届きます。
ガンビン2015

12
うわー、どうもありがとう、今では約束についてもっと理解しています。約束についてたくさん読んでいますが、実際のコードでそれらを使用する必要があるまで、すべてのメカニズムを本当に理解しているわけではありません。今、私はそれを良くし、あなたのおかげでクールなものを書き始めることができます。
ガンビン2015

1
あなたは(たとえばあざける進行のために)何らかの理由の順序で完全にこれらのタスクを取得したい場合にも、あなたは変更することができますMath.floor(Math.random() * 1000)(i * 1000)
OKわから

@TJでは、結果データをビューにレンダリングする方法と、ループを実行してデータを表示する方法を説明します
Ajit Singh

1
@ user1063287-コードawaitが許可されているコンテキストにある場合は、これを行うことができます。現時点では、使用できる場所awaitasync関数内のみです。(ある時点で、モジュールのトップレベルでも使用できるようになります。)
TJCrowder19年

6

再利用可能な関数は、このパターンでうまく機能します。

function awaitAll(count, asyncFn) {
  const promises = [];

  for (i = 0; i < count; ++i) {
    promises.push(asyncFn());
  }

  return Promise.all(promises);
}

OPの例:

awaitAll(5, doSomeAsyncStuff)
  .then(results => console.log('doSomeStuffOnlyWhenTheAsyncStuffIsFinished', results))
  .catch(e => console.error(e));

関連するパターンは、配列を反復処理し、各アイテムに対して非同期操作を実行することです。

function awaitAll(list, asyncFn) {
  const promises = [];

  list.forEach(x => {
    promises.push(asyncFn(x));
  });

  return Promise.all(promises);
}

例:

const books = [{ id: 1, name: 'foo' }, { id: 2, name: 'bar' }];

function doSomeAsyncStuffWith(book) {
  return Promise.resolve(book.name);
}

awaitAll(books, doSomeAsyncStuffWith)
  .then(results => console.log('doSomeStuffOnlyWhenTheAsyncStuffIsFinished', results))
  .catch(e => console.error(e));

1
これにより、コードが本当に理解しやすくなり、クリーンになります。現在の例(明らかにOPのコードに適合されている)がこの正義を行っているとは思いません。これは巧妙なトリックです、ありがとう!
ShaunVermaak20年

2
const doSomeAsyncStuff = async (funcs) => {
  const allPromises = funcs.map(func => func());
  return await Promise.all(allPromises);
}

doSomeAsyncStuff([
  () => new Promise(resolve => setTimeout(() => resolve(), 100)),
  () => new Promise(resolve => setTimeout(() => resolve(), 100)),
  () => new Promise(resolve => setTimeout(() => resolve(), 100)),
  () => new Promise(resolve => setTimeout(() => resolve(), 100)),
  () => new Promise(resolve => setTimeout(() => resolve(), 100)),
]);

2

ここに記載されている答えを理解するために私が自分で書いたコードは次のとおりです。forループにマングースクエリがあるのでasyncFunction、代わりにここに配置します。それが誰かに役立つことを願っています。このスクリプトは、ノードまたは多くのJavascriptランタイムのいずれかで実行できます。

let asyncFunction = function(value, callback)
{
        setTimeout(function(){console.log(value); callback();}, 1000);
}



// a sample function run without promises

asyncFunction(10,
    function()
    {
        console.log("I'm back 10");
    }
);


//here we use promises

let promisesArray = [];

let p = new Promise(function(resolve)
{
    asyncFunction(20,
        function()
        {
            console.log("I'm back 20");
            resolve(20);
        }
    );
});

promisesArray.push(p);


for(let i = 30; i < 80; i += 10)
{
    let p = new Promise(function(resolve)
    {
        asyncFunction(i,
            function()
            {
                console.log("I'm back " + i);
                resolve(i);
            }
        );
    });
    promisesArray.push(p);
}


// We use Promise.all to execute code after all promises are done.

Promise.all(promisesArray).then(
    function()
    {
        console.log("all promises resolved!");
    }
)

1

/*** Worst way ***/
for(i=0;i<10000;i++){
  let data = await axios.get(
    "https://yourwebsite.com/get_my_data/"
  )
  //do the statements and operations
  //that are dependant on data
}

//Your final statements and operations
//That will be performed when the loop ends

//=> this approach will perform very slow as all the api call
// will happen in series


/*** One of the Best way ***/

const yourAsyncFunction = async (anyParams) => {
  let data = await axios.get(
    "https://yourwebsite.com/get_my_data/"
  )
  //all you statements and operations here
  //that are dependant on data
}
var promises = []
for(i=0;i<10000;i++){
  promises.push(yourAsyncFunction(i))
}
await Promise.all(promises)
//Your final statement / operations
//that will run once the loop ends

//=> this approach will perform very fast as all the api call
// will happen in parallal

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