如何知道所有Promise何时被动态“可迭代”参数解析?

时间:2016-06-14 01:38:13

标签: javascript promise ecmascript-6

我的问题是我不知道如何知道动态promise数组何时解决了所有的promise。

这是一个例子:

var promiseArray = [];
promiseArray.push(new Promise(){/*blablabla*/});
promiseArray.push(new Promise(){/*blablabla*/});
Promise.all(promiseArray).then(function(){
    // This will be executen when those 2 promises are solved.
});
promiseArray.push(new Promise(){/*blablabla*/});

我这里有问题。当前两个承诺被解决时,Promise.all行为将被执行,但是,在这两个承诺被解决之前,第三个承诺被添加,而新的承诺将不被考虑。

所以,我需要的是说:“嘿Promise.all,你有一个动态数组来检查”。我该怎么办?

请记住,这只是一个例子。我知道我可以将行Promise.all移到最后一行,但实际上新的promise会在另一个promises被解决时动态添加,而新的promise也可以添加新的promise,因此,它是一个非常动态的数组。

我拥有的真实用例是这样的:

  1. 我使用Twitter API检查是否有新的推文(使用搜索Api)。
  2. 如果我发现了新的推文,我将它添加到MongoDB(这里我们有Promises)。
  3. 如果这些新推文与我在MongoDB中没有的用户相关(这里我们有新的承诺,因为我必须去MongoDB检查我是否有该用户),我们转到Twitter API获取用户信息(更多承诺),我们将这些新用户添加到MongoDB(是的,更多的承诺)。
  4. 然后,我转到MongoDB插入新值以将新推文与这些新用户相关联(更多承诺!wiii!)。
  5. 当所有对MongoDB的查询都已解决(所有选择,更新,插入)时,关闭MongoDB连接。
  6. 另一个难的例子:

    var allPromises = [];
    
    allPromises.push(new Promise(function(done, fail){
        mongoDB.connect(function(error){
            //Because mongoDB works with callbacks instead of promises
            if(error)
                fail();
            else
                ajax.get('/whatever').then(function(){
                    if (somethingHappens) {
                        allPromises.push(new Promise(function(done, fail){ //This promise never will be take in account
                            // bla bla bla
                            if (somethingHappens) {
                                allPromises.push(new Promise(function(done, fail){ //This promise never will be take in account
                                    // bla bla bla
                                }));
                            } else {
                                ajax.get('/whatever/2').then(function(){
                                    if (somethingHappens) {
                                        allPromises.push(new Promise(function(done, fail){ //This promise never will be take in account
                                            // bla bla bla
                                        }));
                                    }
                                });
                            }
                        }));
                    } else {
                        ajax.get('/whatever/2').then(function(){
                            if (somethingHappens) {
                                allPromises.push(new Promise(function(done, fail){ //This promise never will be take in account
                                    // bla bla bla
                                        if (somethingHappens) {
                                            allPromises.push(new Promise(function(done, fail){ //This promise never will be take in account
                                                // bla bla bla
                                            }));
                                        } else {
                                            ajax.get('/whatever/2').then(function(){
                                                if (somethingHappens) {
                                                    allPromises.push(new Promise(function(done, fail){ //This promise never will be take in account
                                                        // bla bla bla
                                                    }));
                                                }
                                            });
                                        }
                                }));
                            }
                        });
                    }
                });
        });
    }));
    
    Promise.all(allPromises).then(function(){
        // Soooo, all work is done!
        mongodb.close()!
    });
    

    所以,现在,一个美容的例子。当调用最后一个(我们不知道哪个是最后一个)promise时,我们需要调用showAllTheInformation函数。你是怎么做到的?:

    var name = 'anonimus';
    var date = 'we do not know';
    
    function userClikOnLogIn() {
        $http.get('/login/user/password').then(function(data){
            if (data.logguedOk) {
                $http.get('/checkIfIsAdmin').then(function(data){
                    if (data.yesHeIsAnAdmin) {
                        $http.get('/getTheNameOfTheUser').then(function(data){
                            if(data.userHasName) {
                                $http.get('/getCurrentDate').then(function(data){
                                    currentDate = data.theNewCurrentDate;
                                });
                            }
                        });
                    }
                });
            }
        });
    }
    
    function showAllTheInformation() {
        alert('Hi ' + name + ' today is:' + date);
    }
    

    这里有更多上下文的另一个例子: https://jsfiddle.net/f0a1s79o/2/

5 个答案:

答案 0 :(得分:11)

您可以创建一个简洁的小递归函数来包装Promise.all以处理添加到原始承诺:



/**
 * Returns a Promise that resolves to an array of inputs, like Promise.all.
 *
 * If additional unresolved promises are added to the passed-in iterable or
 * array, the returned Promise will additionally wait for those, as long as
 * they are added before the final promise in the iterable can resolve.
 */
function iterablePromise(iterable) {
  return Promise.all(iterable).then(function(resolvedIterable) {
    if (iterable.length != resolvedIterable.length) {
      // The list of promises or values changed. Return a new Promise.
      // The original promise won't resolve until the new one does.
      return iterablePromise(iterable);
    }
    // The list of promises or values stayed the same.
    // Return results immediately.
    return resolvedIterable;
  });
}

/* Test harness below */

function timeoutPromise(string, timeoutMs) {
  console.log("Promise created: " + string + " - " + timeoutMs + "ms");
  return new Promise(function(resolve, reject) {
    window.setTimeout(function() {
      console.log("Promise resolved: " + string + " - " + timeoutMs + "ms");
      resolve();
    }, timeoutMs);
  });
}

var list = [timeoutPromise('original', 1000)];
timeoutPromise('list adder', 200).then(function() {
  list.push(timeoutPromise('newly created promise', 2000));
});
iterablePromise(list).then(function() { console.log("All done!"); });




请记住,这仅仅包括添加内容,并且它仍然有点危险:您需要确保回调订单是这样的,即飞行中的任何承诺将自己添加到列表之前可以调用Promises.all回调。

答案 1 :(得分:2)

没有出路。在调用Promise.all之前,你必须将所有的promises放在数组中。在您提供的示例中,这就像将最后一行移到顶部一样简单。

如果您异步填充数组,则应该获得该数组的承诺,并使用.then(Promise.all.bind(Promise))。如果你不知道什么时候停止添加承诺,那么这是不可能的,因为它们可能永远都不会被解决。

关于您的“美容示例”,您需要了解magic of chaining。正如我在评论中所说的那样,你必须return来自你正在做异步的每个函数的承诺。实际上,只需添加缺少的return s:

function userClikOnLogIn() {
    return $http.get('/login/user/password').then(function(data){
//  ^^^^^^
        if (data.logguedOk) {
            return $http.get('/checkIfIsAdmin').then(function(data){
//          ^^^^^^
                if (data.yesHeIsAnAdmin) {
                    return $http.get('/getTheNameOfTheUser').then(function(data){
//                  ^^^^^^
                        if(data.userHasName) {
                            return $http.get('/getCurrentDate').then(function(data){
//                          ^^^^^^
                                currentDate = data.theNewCurrentDate;
                            });
                        }
                    });
                }
            });
        }
    });
}

userClikOnLogIn().then(function showAllTheInformation() {
//               ^^^^^ now you can chain onto it!
    alert('Hi ' + name + ' today is:' + date);
});

这里没有任何承诺数组会动态增长,只是每个函数都返回它所做事物的(异步)结果的承诺。

答案 2 :(得分:2)

我知道我在这里参加聚会迟到了。但是,对于那些感到难过却找不到快速答案的人,这是一种肮脏的方法(稍后解释),而无需重新架构,前提是您确定在完成所有任务后不会添加新的承诺现有的诺言。

var promiseArray = [], completedPromises = [];
promiseArray.push(new Promise(){/*blablabla1*/});
promiseArray.push(new Promise(){/*blablabla2*/});
while(completedPromises.length != promiseArray.length) completedPromises = await Promise.all(promiseArray);

代码中的其他位置(在完成所有先前的承诺之前:

promiseArray.push(new Promise(){/*blablabla3*/});

希望这对某人有帮助。经过多年的免费下载,我才创建了一个堆栈溢出帐户:)

答案 3 :(得分:1)

如果您可以检查承诺或其使用情况,并确定范围问题,那么我认为您可以更简单地解决问题:承诺有多少承诺?

换句话说,你不需要跟踪所有的承诺,只需计算它们。

var outstanding = 0;

var p1 = new Promise(){/*blablabla*/};
var p2 = new Promise(){/*blablabla*/};

++outstanding;
p1.then( (data) => { ...
  if (0 >= --outstanding) 
    // All resolved!
}

// dynamic set of promises, so later we decide to add another:
var p3 = new Promise(){/*blablabla*/};
++outstanding;
p3.then( ... );  // as above

为了改进上述内容,将其全部包含在元承诺中(与Promise.all为一组静态承诺返回的承诺相等)...

  // Create a promise that tracks completion of a dynamic set of instrumented promises.
  getCompletionP() { 
    let rslv = null;
    const p = new Promise(
      function(resolve, reject) {
        rslv = resolve;
      } );
    p.resolve = rslv;
    assert( p.resolve );
    p.scheduled = 0;
    p.onSchedule = function() { ++this.scheduled; };
    p.onComplete = function()  { if (0 >= --this.scheduled) this.resolve(); };
    return p;
  }

现在在每次调用then()之前调用cp.onSchedule(),然后在每个then()结束时调用cp.onComplete,并且cp将在所有promise之后解析,然后函数完成。 (当然,你也需要处理catch语句。)

当通过Promise.then调用完成的所有代码完成时,这将解决,而问题要求解决所有promises时解决的问题。这可以通过在promises的resolve语句之后添加调用来实现,但是如果使用第三方库则这是不可能的,并且我认为它们在功能上是相同的。

这不适用于所有情况,但由于接受的答案是它(动态的承诺集)无法完成,我认为这仍然有用虽然它变得更复杂(凌乱),因为我写的它出来了!

答案 4 :(得分:0)

@JeffBowman和@Bergi有正确的想法:递归等待和计数承诺。这是我在Coffeescript中的实现)

Promise = require 'bluebird'

class DynamicPromiseCollection

    promises = []

    add:(p)->
        promises.push p

    wait_for_all:->
        #
        # Wait for all current promises, then check for new promises...
        # ...if there are new promises, then keep waiting ( recursively ).
        #
        # Resolve only when all promises are done, and there are no new promises.
        #
        make_promise = ->
            num_before = promises.length
            p = Promise.all(promises).then ->
                num_after = promises.length
                if num_after > num_before
                    return make_promise() # recursive -- wait again
                else
                    return true # all done now
        p = make_promise()
        return p


#
# let's test this...
#
promises = new DynamicPromiseCollection()


#
# pretend to get remote data
#
get_remote_data = ->
    new Promise (resolve,reject)->
        setTimeout ->
            resolve "data"
        ,500

#
# get data, wait, then get more data...
#
promises.add get_remote_data().then (data)->
    console.log "got " + data
    promises.add get_remote_data().then (data)->
        console.log "got " + data

#
# this should wait for both data
#
promises.wait_for_all().then ->
    console.log "...and wait_for_all is done."