向Promise.all()添加承诺

时间:2017-02-13 17:42:46

标签: javascript promise es6-promise

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

这是我的尝试。我希望可以添加新的承诺,并且一旦完成,Promise.all就会解决。

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

有没有办法“重新融合”Promise.all()?

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);
    });
}   

所需的流程类似于:

  1. 创建一组承诺。
  2. 一些承诺产生了更多的承诺。
  3. 一旦所有最初的承诺和产生的承诺得到解决,请致电回叫。

2 个答案:

答案 0 :(得分:3)

看起来总体目标是:

  1. 对于urls中的每个条目,请致电$.get并等待其完成。
    • 如果只返回没有“下一步”的响应,请保留一个响应
    • 如果它返回带有“next”的响应,我们也想要请求“next”,然后保留它们。
  2. 完成所有工作后,使用response调用回调。
  3. 我会更改#2,因此您只需返回承诺并使用response解决此问题。

    关于承诺的关键是then会返回一个承诺,该承诺将通过您从then返回的内容得到解决(如果您返回非{ -thenable值,或间接的,如果你返回一个thenable,通过奴役自己的那个)。这意味着如果你有一个承诺来源(在这种情况下为$.get),你几乎不需要使用new Promise;只需使用您使用then创建的承诺。 (和catch。)

    见评论:

    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;我们将错误处理推迟给调用者。

    用法:

    testCase(["url1", "url2", "etc."])
        .then(function(responses) {
            // Use `responses` here
        })
        .catch(function(error) {
            // Handle error here
        });
    

    testCase函数看起来很长,但这只是因为评论。这里没有他们:

    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的箭头功能,它会更简洁。 : - )

    在评论中你问过:

      

    如果有下一个下一个可以处理吗?喜欢第3页的结果?

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

    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更简单:

    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;
        });
    }
    

答案 1 :(得分:1)

假设您使用的是jQuery v3 +,您可以使用$.ajax返回的承诺传递给Promise.all()

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

简化示例

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