web-dev-qa-db-ja.com

複数の非同期関数を実行してからコールバックを実行する方法

NodeJSコードでは、2つまたは3つのAPI呼び出しを行う必要があり、それぞれがいくつかのデータを返します。すべてのAPI呼び出しが完了したら、すべてのデータを単一のJSONオブジェクトに収集して、フロントエンドに送信します。

私はAPIコールバックを使用してこれを行う方法を知っています(次の呼び出しは前の呼び出しのコールバックで発生します)が、これは遅いでしょう:

//1st request
request('http://www.example.com', function (err1, res1, body) {

  //2nd request
  request('http://www.example2.com', function (err2, res2, body2) {

    //combine data and do something with it

  });

});

約束で似たようなきちんとしたこともできることはわかっていますが、現在の呼び出しが終了するまで次の呼び出しが実行されない場合にも同じ概念が当てはまると思います。

すべての関数を同時に呼び出す方法はありますが、コードの最後のブロックで、すべてのAPI呼び出しが完了するまで待機してから実行する前にデータを提供しますか?

27
Coop

PromiseはPromise.all()を提供します(これはネイティブのPromiseとbluebirdのようなライブラリのPromiseにも当てはまります)。

更新:Node 8なので、Bluebirdのutil.promisify()と同様に.promisify()を使用できます。

var requestAsync = util.promisify(request); // const util = require('util')
var urls = ['url1', 'url2'];
Promise.all(urls.map(requestAsync)).then(allData => {
    // All data available here in the order of the elements in the array
});

できること(ネイティブ):

function requestAsync(url) {
    return new Promise(function(resolve, reject) {
        request(url, function(err, res, body) {
            if (err) { return reject(err); }
            return resolve([res, body]);
        });
    });
}
Promise.all([requestAsync('url1'), requestAsync('url2')])
    .then(function(allData) {
        // All data available here in the order it was called.
    });

Bluebirdがある場合、これはさらに簡単です。

var requestAsync = Promise.promisify(request);
var urls = ['url1', 'url2'];
Promise.all(urls.map(requestAsync)).then(allData => {
    // All data available here in the order of the elements in the array
});
48
Madara Uchiha

Async.parallel()のようなサウンドも、asyncを使用したい場合に仕事をします:

var async = require('async');

async.parallel({
    one: function(parallelCb) {
        request('http://www.example1.com', function (err, res, body) {
            parallelCb(null, {err: err, res: res, body: body});
        });
    },
    two: function(parallelCb) {
        request('http://www.example2.com', function (err, res, body) {
            parallelCb(null, {err: err, res: res, body: body});
        });
    },
    three: function(parallelCb) {
        request('http://www.example3.com', function (err, res, body) {
            parallelCb(null, {err: err, res: res, body: body});
        });
    }
}, function(err, results) {
    // results will have the results of all 3
    console.log(results.one);
    console.log(results.two);
    console.log(results.three);
});
12
Ben

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/all

Promise.allがES6に含まれるようになったため、サードパーティのライブラリはまったく必要ありません。

「Promise.allはすべてのフルフィルメントを待機します(または最初の拒否)」

リファクタリングitterationsでPromise.all()を実証するためにGistをセットアップしました: https://Gist.github.com/rainabba/21bf3b741c6f9857d741b69ba8ad78b1

IIFE(即時に関与する関数式)を使用しています。慣れていない場合は、GistがIIFEの使用方法を示していますが、以下の例を参考にしてください。 https://en.wikipedia.org/wiki/Immediately-invoked_function_expression

TL; DR

( function( promises ){
    return new Promise( ( resolve, reject ) => {
        Promise.all( promises )
            .then( values => {
                console.log("resolved all promises")
                console.dir( values );
                resolve( values.reduce( (sum,value) => { return sum+value }) ); //Use Array.prototype.reduce() to sum the values in the array
            })
            .catch( err => {
                console.dir( err );
                throw err;
            });

    });
})([ 
    new Promise( ( resolve, reject ) => {
        console.log("resolving 1");
        resolve( 1 );
    }),
    new Promise( ( resolve, reject ) => {
        console.log("resolving 2");
        resolve( 2 );
    })
 ]).then( sum => { console.dir( { sum: sum } ) } )
1
rainabba