【问题标题】:Adding a Promise to Promise.all() [duplicate]向 Promise.all() 添加 Promise [重复]
【发布时间】:2017-07-01 19:13:48
【问题描述】:

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

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

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

有没有办法“重新初始化” 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. 在所有初始承诺和衍生承诺解决后,调用回调。

【问题讨论】:

  • 你在哪里初始化promises?我看到你在推动它,但我没有看到你创造它。
  • url 是什么? (如果是数组,通常是复数形式,例如urls。)
  • 为什么是response.resource = response.resource.concat(v.resource);?每次都会创建一个全新的数组...?
  • @T.J.Crowder - 谢谢你的收获。我已经清理了一些测试用例。这不是我的生产代码,只是演示问题。
  • 如果一个响应有response.meta.next,你想要both那个原始响应和结果中的“下一个”响应吗?

标签: javascript promise es6-promise


【解决方案1】:

看起来总体目标是:

  1. 对于urls 中的每个条目,调用$.get 并等待它完成。
    • 如果它只返回一个没有“下一个”的响应,则保留那个响应
    • 如果它返回带有“下一个”的响应,我们希望也请求“下一个”,然后保留它们。
  2. 所有工作完成后,使用response 调用回调。

我会更改 #2,因此您只需返回承诺并使用 response 履行它。

关于 Promise 的一个关键是 then 返回一个 new Promise,它将根据您返回的内容来解决:如果您返回一个非 thenable 值,则该 Promise 将通过该值实现价值;如果您返回一个 thenable,则承诺将解析为您返回的 thenable。这意味着如果你有一个 Promise 的来源(在这种情况下是$.get),你几乎不需要使用new Promise;只需使用您通过then 创建的承诺。 (还有catch。)

(如果“thenable”一词不熟悉,或者您不清楚“fulfill”和“resolve”之间的区别,我会在我的博客上的this post 中介绍promise 术语。)

见 cmets:

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 fulfill (via `return`)
                    // with an array with both the original response and the
                    // "next". Note that by returning a thenable, we resolve the
                    // promise created by `then` 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 函数看起来很长,但这仅仅是因为 cmets。这里没有它们:

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

【讨论】:

  • 如果有下一个next,这个可以处理吗?喜欢第 3 页的结果?
  • @Josiah:它可以,是的。我们必须改变逻辑。我们可能想要一个函数来处理给定的 URL,并返回一个 Promise,该 Promise 将通过所有“nexts”的响应数组来解决,并递归地使用它。你想给它一个 URL 和一个偏移量或类似的东西。
  • @Josiah:事实证明这样做很简单,我已将其添加到答案的末尾。
  • 在我的旧版本中,我使用递归函数来获取信息。我以为我可以使用生成承诺的回调来做到这一点,但我认为这可能更干净。非常感谢!
  • @Josiah:别担心! getToEnd 是递归的,请注意。 :-)
【解决方案2】:

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

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

简化示例

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

【讨论】:

    猜你喜欢
    • 2020-02-20
    • 2022-12-01
    • 2021-01-23
    • 2017-04-24
    • 2018-06-14
    • 2016-03-22
    • 2020-08-06
    • 1970-01-01
    • 2018-02-06
    相关资源
    最近更新 更多