关于javascript:如何知道何时在动态“可迭代”参数中解决所有承诺?

How to know when all Promises are Resolved in a dynamic “iterable” parameter?

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

这里是一个例子:

1
2
3
4
5
6
7
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,并且新的Promise也可以添加新的Promise,因此,这是一个真正的动态数组。

我真正的用例是这样的:

  • 我使用Twitter API来检查是否有新的Tweets(使用Search Api)。
  • 如果我发现了新的Tweets,可以将其添加到MongoDB(这里有Promises)。
  • 如果这些新的Tweets与我在MongoDB中没有的用户相关(在这里我们有了新的承诺,因为我必须去MongoDB来检查我是否有该用户),我们将使用Twitter API来获取用户信息(更多承诺),然后将这些新用户添加到MongoDB中(是,更多承诺)。
  • 然后,我去MongoDB插入新值,以将新推文与那些新用户相关联(更多诺言!wiii!)。
  • 解决了所有对MongoDB的查询(所有选择,更新,插入)后,关闭MongoDB连接。
  • 另一个困难的例子:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    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函数。你怎么做呢?:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    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/


    您可以制作一个简洁的递归函数来包装Promise.all,以处理对原始promise的添加:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    /**
     * 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回调之前,所有运行中的诺言都会将其自身添加到列表中。


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

    如果要异步填充数组,则应获得对该数组的承诺,并使用.then(Promise.all.bind(Promise))。如果您不知道何时停止添加承诺,那么这将是不可能的,因为它们可能根本不会全部解决。

    关于"美容示例",您将需要学习链接的魔力。正如我之前在评论中所说的那样,您必须return每个正在执行异步操作的函数的承诺。实际上,只需添加缺少的return

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    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);
    });

    这里没有动态增长的诺言数组,只是每个函数都针对其所做的(异步)结果返回诺言。


    如果您可以兑现承诺或承诺的使用,而范围界定问题允许这样做,那么我认为您可以更简单地解决问题:有多少个承诺?

    换句话说,您无需跟踪所有的承诺,只需计算它们即可。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    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

    为了改善上述内容,请将其全部包装到meta-promise中(相当于Promise.all将返回的一组静态Promise)。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
      // 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将解析,然后函数完成。 (当然,您也需要处理catch语句。)

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

    这并非在所有情况下都有效,但是由于公认的答案是无法完成(动态的Promise),所以我认为尽管我写了它变得更加复杂(混乱),但这仍然有用。


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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    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."