关于javascript:向Promise.all()添加一个Promise

Adding a Promise to Promise.all()

本问题已经有最佳答案,请猛点这里访问。

我有一个api调用,有时会返回分页的响应。 我想自动将这些添加到我的诺言中,以便在所有数据到达后获取回调。

这是我的尝试。 我希望添加新的承诺,并在完成后解决Promise.all。

实际发生的是Promise.all不等待第二个请求。 我的猜测是Promise.all在调用时会附加"侦听器"。

有没有办法"重新初始化" Promise.all()?

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
function testCase (urls, callback) {
    var promises = [];
    $.each(urls, function (k, v) {
        promises.push(new Promise(function(resolve, reject) {
            $.get(v, function(response) {
                if (response.meta && response.meta.next) {
                    promises.push(new Promise(function (resolve, reject) {
                        $.get(v + '&offset=' + response.meta.next, function (response) {
                            resolve(response);
                        });
                    }));
                }
                resolve(response);
            }).fail(function(e) {reject(e)});
        }));
    });

    Promise.all(promises).then(function (data) {
        var response = {resource: []};
        $.each(data, function (i, v) {
            response.resource = response.resource.concat(v.resource);
        });
        callback(response);
    }).catch(function (e) {
        console.log(e);
    });
}

所需的流程类似于:

  • 创建一组承诺。
  • 一些承诺产生更多的承诺。
  • 一旦所有最初的承诺和衍生的承诺都解决,请调用回调。

  • 看起来总体目标是:

  • 对于urls中的每个条目,请调用$.get并等待其完成。

    • 如果它只返回一个响应而不包含" next",则保留该响应
    • 如果它返回带有" next"的响应,我们也要请求" next",然后将它们都保留。
  • 完成所有工作后,使用response调用回调。
  • 我将更改#2,以便您只返回诺言并使用response来解决它。

    关于promise的关键是then返回一个新的promise,可以用您从then返回的结果来解决(直接,如果您返回一个非thentheable值,或者间接地如果您返回一个thenable,则是通过奴隶本身就可以)。这意味着,如果您有承诺的来源(在本例中为$.get),则几乎无需使用new Promise;只需使用您通过then创建的承诺即可。 (和catch。)

    看评论:

    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
    function testCase(urls) {
        // Return a promise that will be settled when the various `$.get` calls are
        // done.
        return Promise.all(urls.map(function(url) {
            // Return a promise for this `$.get`.
            return $.get(url)
                .then(function(response) {
                    if (response.meta && response.meta.next) {
                        // This `$.get` has a"next", so return a promise waiting
                        // for the"next" which we ultimately resolve (via `return`)
                        // with an array with both the original response and the
                        //"next". Note that since we're returning a thenable, the
                        // promise created by `then` will slave itself to the
                        // thenable we return.
                        return $.get(url +"&offset=" + response.meta.next)
                            .then(function(nextResponse) {
                                return [response, nextResponse];
                            });
                    } else {
                        // This `$.get` didn't have a"next", so resolve this promise
                        // directly (via `return`) with an array (to be consistent
                        // with the above) with just the one response in it. Since
                        // what we're returning isn't thenable, the promise `then`
                        // returns is resolved with it.
                        return [response];
                    }
                });
        })).then(function(responses) {
            // `responses` is now an array of arrays, where some of those will be one
            // entry long, and others will be two (original response and next).
            // Flatten it, and return it, which will settle he overall promise with
            // the flattened array.
            var flat = [];
            responses.forEach(function(responseArray) {
                // Push all promises from `responseArray` into `flat`.
                flat.push.apply(flat, responseArray);
            });
            return flat;
        });
    }

    请注意,我们在那里永远不使用catch;我们将错误处理延迟给调用者。

    用法:

    1
    2
    3
    4
    5
    6
    7
    testCase(["url1","url2","etc."])
        .then(function(responses) {
            // Use `responses` here
        })
        .catch(function(error) {
            // Handle error here
        });

    testCase函数看起来真的很长,但这仅仅是因为注释。这里没有他们:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    function testCase(urls) {
        return Promise.all(urls.map(function(url) {
            return $.get(url)
                .then(function(response) {
                    if (response.meta && response.meta.next) {
                        return $.get(url +"&offset=" + response.meta.next)
                            .then(function(nextResponse) {
                                return [response, nextResponse];
                            });
                    } else {
                        return [response];
                    }
                });
        })).then(function(responses) {
            var flat = [];
            responses.forEach(function(responseArray) {
                flat.push.apply(flat, responseArray);
            });
            return flat;
        });
    }

    ...如果我们使用ES2015的arrow函数,它将更加简洁。 :-)

    在评论中,您询问:

    Could this handle if there was a next next? Like a page 3 of results?

    我们可以通过将该逻辑封装到我们使用的函数中来代替$.get,而我们可以递归地使用它:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    function getToEnd(url, target, offset) {
        // If we don't have a target array to fill in yet, create it
        if (!target) {
            target = [];
        }
        return $.get(url + (offset ?"&offset=" + offset :""))
            .then(function(response) {
                target.push(response);
                if (response.meta && response.meta.next) {
                    // Keep going, recursively
                    return getToEnd(url, target, response.meta.next);
                } else {
                    // Done, return the target
                    return target;
                }
            });
    }

    然后我们的主要testCase更简单:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    function testCase(urls) {
        return Promise.all(urls.map(function(url) {
            return getToEnd(url);
        })).then(function(responses) {
            var flat = [];
            responses.forEach(function(responseArray) {
                flat.push.apply(flat, responseArray);
            });
            return flat;
        });
    }


    假设您正在使用jQuery v3 +,则可以使用$.ajax返回的promise传递给Promise.all()

    您缺少的是将第二个请求作为promise返回,而不是尝试将其推送到promise数组

    简化的例子

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    var promises = urls.map(function(url) {
      // return promise returned by `$.ajax`
      return $.get(url).then(function(response) {
        if (response.meta) {
          // return a new promise
          return $.get('special-data.json').then(function(innerResponse) {
            // return innerResponse to resolve promise chain
            return innerResponse;
          });

        } else {
          // or resolve with first response
          return response;
        }
      });

    })

    Promise.all(promises).then(function(data) {
      console.dir(data)
    }).catch(function(e) {
      console.log(e);
    });

    DEMO