120

次のプロミス呼び出しとチェーンされたlogger.log(res)が反復を通じて同期的に実行されることを確認するためにループを正しく構築する方法は? (青い鳥)

db.getUser(email).then(function(res) { logger.log(res); }); // this is a promise

次の方法を試しました(http://blog.victorquinn.com/javascript-promise-while-loopの方法)

var Promise = require('bluebird');

var promiseWhile = function(condition, action) {
    var resolver = Promise.defer();

    var loop = function() {
        if (!condition()) return resolver.resolve();
        return Promise.cast(action())
            .then(loop)
            .catch(resolver.reject);
    };

    process.nextTick(loop);

    return resolver.promise;
});

var count = 0;
promiseWhile(function() {
    return count < 10;
}, function() {
    return new Promise(function(resolve, reject) {
        db.getUser(email)
          .then(function(res) { 
              logger.log(res); 
              count++;
              resolve();
          });
    }); 
}).then(function() {
    console.log('all done');
}); 

動作しているように見えますが、 logger.log(res);を呼び出す順序が保証されているとは思いません。

助言がありますか?

4

13 に答える 13

139

promiseWhen()この目的やその他の目的で一般的な関数が本当に必要な場合は、Bergi の単純化を使用して、ぜひそうしてください。ただし、promise の仕組み上、この方法でコールバックを渡すことは通常不要であり、複雑で小さなフープをジャンプする必要があります。

あなたがしようとしていると私が言える限り:

  • 電子メール アドレスのコレクションの一連のユーザー詳細を非同期的にフェッチする (少なくとも、これが唯一の理にかなっているシナリオです)。
  • これを行うには、.then()再帰を介してチェーンを構築します。
  • 返された結果を処理するときに元の順序を維持します。

このように定義された問題は、実際にはPromise Anti-patternsの「The Collection Kerfuffle」で説明されているものであり、2 つの単純な解決策を提供しています。

  • を使用した並列非同期呼び出しArray.prototype.map()
  • を使用したシリアル非同期呼び出しArray.prototype.reduce()

並列アプローチは、(率直に)回避しようとしている問題、つまり応答の順序が不確実であるという問題を引き起こします。シリアル アプローチは、必要な.then()チェーンを構築します - フラット - 再帰はありません。

function fetchUserDetails(arr) {
    return arr.reduce(function(promise, email) {
        return promise.then(function() {
            return db.getUser(email).done(function(res) {
                logger.log(res);
            });
        });
    }, Promise.resolve());
}

次のように呼び出します。

//Compose here, by whatever means, an array of email addresses.
var arrayOfEmailAddys = [...];

fetchUserDetails(arrayOfEmailAddys).then(function() {
    console.log('all done');
});

ご覧のとおり、醜い外部変数countやそれに関連するcondition関数は必要ありません。制限 (問題では 10) は、配列の長さによって完全に決定されarrayOfEmailAddysます。

于 2014-07-27T20:42:34.610 に答える
40

これが、標準の Promise オブジェクトで行う方法です。

// Given async function sayHi
function sayHi() {
  return new Promise((resolve) => {
    setTimeout(() => {
      console.log('Hi');
      resolve();
    }, 3000);
  });
}

// And an array of async functions to loop through
const asyncArray = [sayHi, sayHi, sayHi];

// We create the start of a promise chain
let chain = Promise.resolve();

// And append each function in the array to the promise chain
for (const func of asyncArray) {
  chain = chain.then(func);
}

// Output:
// Hi
// Hi (After 3 seconds)
// Hi (After 3 more seconds)
于 2016-07-25T17:56:04.230 に答える
0

BlueBirdを使ったこれはどうですか?

function fetchUserDetails(arr) {
    return Promise.each(arr, function(email) {
        return db.getUser(email).done(function(res) {
            logger.log(res);
        });
    });
}
于 2016-10-01T18:15:48.280 に答える
0

標準の promise オブジェクトを使用し、promise が結果を返すようにします。

function promiseMap (data, f) {
  const reducer = (promise, x) =>
    promise.then(acc => f(x).then(y => acc.push(y) && acc))
  return data.reduce(reducer, Promise.resolve([]))
}

var emails = []

function getUser(email) {
  return db.getUser(email)
}

promiseMap(emails, getUser).then(emails => {
  console.log(emails)
})
于 2018-10-30T01:12:04.760 に答える