【问题标题】:Setting a timeout for each promise within a promise.all为 promise.all 中的每个 promise 设置超时
【发布时间】:2021-09-12 13:12:19
【问题描述】:

我能够成功执行 Promise.all,并优雅地处理解析和拒绝。但是,有些承诺会在几毫秒内完成,有些可能/可能需要一段时间。

我希望能够为 Promise.all 中的每个 Promise 设置一个超时,因此它可以尝试最多花费 5 秒。

getData() {
    var that = this;
    var tableUrls = ['http://table-one.com','http://table-two.com'];
    var spoonUrls = ['http://spoon-one.com','http://spoon-two.com'];

    var tablePromises = that.createPromise(tableUrls);
    var spoonPromises = that.createPromise(spoonUrls);
    var responses = {};

    var getTableData = () => {
        var promise = new Promise((resolve, reject) => {
            Promise.all(tablePromises.map(that.rejectResolveHandle))
                .then((results) => {
                    responses.tables = results.filter(x => x.status === 'resolved');
                    resolve(responses);
                });
        });
        return promise;
    };

    var getSpoonData = () => {
        var promise = new Promise((resolve, reject) => {
            Promise.all(spoonPromises.map(that.rejectResolveHandle))
                .then((results) => {
                    responses.tables = results.filter(x => x.status === 'resolved');
                    resolve(responses);
                });
        });
        return promise;
    };


    return getTableData()
        .then(getSpoonData);
}

rejectResolveHandle() {
    return promise.then(function(v) {
        return {v:v, status: "resolved"};
    }, function(e) {
        return {e:e, status: "rejected"};
    });
}

createPromise(links) {
    var promises = [];
    angular.forEach(links, function (link) {
        var promise = that._$http({
            method: 'GET',
            url: link + '/my/end/point',
            responseType: 'json'
        });
        promises.push(promise);
    });

    return promises;
}

我尝试向createPromise() 添加超时,但这似乎不起作用。将超时设置为 300 毫秒,一些请求会持续 4 秒以上:

createPromise(links) {
    var promises = [];
    angular.forEach(links, function (link) {
        var promise = that._$http({
            method: 'GET',
            url: link + '/my/end/point',
            responseType: 'json'
        });

        promise = new Promise((resolve) => {
            setTimeout(() => {
                resolve(promise);
            }, 300);
        });

        promises.push(promise);
    });

    return promises;
}

如果 Bluebird 能让事情变得更简单,我可以使用它吗?

【问题讨论】:

  • 使用 RxJs / Observables 是一种可能的选择吗?
  • @Pac0 - RxJs 是的,版本 5.4.0... 和 observe-js 版本 0.5.7
  • 你真的想对每一个承诺设置一个超时时间,还是你真的只想要一个计时器,如果任何事情花费的时间太长,就会导致整个事情失败?请记住,如果任何一个失败,整个 Promise.all 都会失败。考虑到这一点,是否有理由不简单地添加一个在......比如说...... 3 秒内失败的包装器,如果整个事情还没有完成?
  • @Norguard - 它应该针对每个承诺,而不是全部。了解整个 Promise.all 可能会失败,以及为什么在 rejectResolveHandle 中处理它们
  • 当询问由您的代码引起的问题时,如果您提供人们可以用来重现问题的代码,您将获得更好的答案。该代码应该是…… Minimal – 使用尽可能少的代码,但仍会产生同样的问题。见How to create a Minimal, Complete, and Verifiable example

标签: javascript angularjs angular-promise es6-promise bluebird


【解决方案1】:

这是一个创建 Promise.raceAll() 函数的方案,它的工作方式有点像 Promise.all()Promise.race() 的组合,其中所有承诺都有超时时间和值,因此如果承诺在该时间之前没有解决,它将被短路以使用传入的值解决。这实际上将每个承诺放入带有计时器的Promise.race()。如果计时器获胜,则使用默认值解决承诺。如果最初的承诺获胜,它会以实际的承诺结果来解决。我们使用Promise.race() 来解决第一个完成的问题(超时或原始承诺)。这是Promise.race() 的经典用法(实际上是我真正用过它的唯一实际用途)。

一个典型的例子是在接下来的 15 秒内为我提供所有结果。任何需要超过 15 秒的结果,只需为它们返回 null 而不要等待它们。下面是使这个概念起作用的代码:

Promise.delay = function(t, val) {
    return new Promise(resolve => {
        setTimeout(resolve.bind(null, val), t);
    });
}

Promise.raceAll = function(promises, timeoutTime, timeoutVal) {
    return Promise.all(promises.map(p => {
        return Promise.race([p, Promise.delay(timeoutTime, timeoutVal)])
    }));
}

因此,您可以像使用Promise.all() 一样使用Promise.raceAll(),因为您向它传递了一个promise 数组,但您也向它传递了一个timeoutTime 和一个timeoutValtimeoutTime 是在超时前等待多长时间。 timeoutVal 是为任何超时的承诺放入结果数组中的内容(通常类似于null,您可以轻松地将其识别为非真实结果)。


我不确定您在特定代码中所做的一切,但这是您使用上述代码的links 代码:

Promise.raceAll(links.map(link => {
    return that._$http({
        method: 'GET',
        url: link + '/my/end/point',
        responseType: 'json'
    });
}), 5000, null).then(results => {
    // process results here
    // any timed out values will be null
    // you can filter out the timed out results
    let final = results.filter(item => !!item);
}).catch(err => {
    // process any errors here
});

或者,如果您想确保 Promise.raceAll() 获得所有结果,即使某些 Promise 被拒绝,您也可以为每个 Promise 添加一个 .catch() 处理程序:

Promise.raceAll(links.map(link => {
    return that._$http({
        method: 'GET',
        url: link + '/my/end/point',
        responseType: 'json'
    }).catch(err => {
        // don't let Promise.all() see a reject so it gets all results
        return null;
    });
}), 5000, null).then(results => {
    // process results here
    // any timed out values will be null
    // you can filter out the timed out or rejected results
    let final = results.filter(item => !!item);
}).catch(err => {
    // process any errors here
});

【讨论】:

  • 我喜欢这个主意,但与我的结合起来会很有用
  • @OamPsy - 我无法准确理解您的问题如何应用于您的特定代码。我使用您的链接数组添加了一个示例。
  • 我应该想到Promise.race!特别是。因为经常有一个类似delay 的函数在周围给我们一个setTimeout 的promise 版本...
  • @OamPsy - 如果您对getTableData()getSpoonData() 的困惑在于如何最好地处理被拒绝的承诺,以便您仍然从Promise.all() 获得所有结果,那么您可以在Promise.settleVal() 中看到这个答案ES6 Promise.all() error handle - Is .settle() needed? 可能对你有用。
  • @OamPsy - 我添加了第二个代码示例,以便Promise.raceAll() 获得所有结果,即使某些承诺被拒绝。
【解决方案2】:

您的尝试只是用超时承诺覆盖 promise,这意味着 HTTP 承诺将被完全忽略。

这是您实际使用new Promise* 的相对少数地方之一:创建一个承诺,根据 HTTP 调用解决或拒绝它,拒绝它(或解决它,但这似乎很奇怪)超时:

createPromise(links) {
    return Promise.all(links.map(function(link) {
        return new Promise((resolve, reject) => {
            that._$http({
                method: 'GET',
                url: link + '/my/end/point',
                responseType: 'json'
            })
            .then(resolve)
            .catch(reject);
            setTimeout(() => {
                reject(/*...relevant value here...*/); // Seems like reject to me, 
                                                       // but you could use resolve
                                                       // if you prefer
            }, 300);
        });
    }));
}

(假设links 是一个数组,则使用map。)

请注意,一旦调用了 resolvereject 以获得承诺,随后对其中任何一个的调用都将被完全忽略(没有错误或警告)。 (如果这不是真的,我们希望在上面有一个标志来跟踪我们是否已经完成了承诺。)


* (除非你有启用承诺的setTimeout 版本。如果你这样做了,你会使用Promise.race,就像jfriend00 在their answer 中所做的那样。)

【讨论】:

  • 谢谢!我可以在控制台中看到一个错误:Uncaught (in promise) {data: null, status: -1, headers: f, config: {}, statusText: ""}
  • @OamPsy:这与上述无关,除非您不处理对createPromise 返回的承诺的拒绝。或者除非links 不是一个数组(我假设它是并使用了map)。
  • 链接,也是一个数组。
  • “除非你不处理对 createPromise 返回的 promise 的拒绝”——是否应该过滤掉拒绝的内容?
  • @OamPsy:对不起,我不小心在代码中遗漏了Promise.all!我上面的描述只有在 map 被包裹在 Promise.all 中时才有效(它本来应该是,现在是刷新)。您无需访问各个承诺的状态。一个 promise createPromise 返回解析或拒绝。
【解决方案3】:

一种解决方案可能是将Promise.all 的参数数组中的每个promise 转换为超时promise(例如使用.map)。超时函数可以根据应用程序需要解析或拒绝提供的参数承诺。

在超时后使用唯一值来解决承诺允许它在提供给Promise.all.then( handler)的结果数组中被识别

本示例着眼于使用辅助函数 timedPromise 将承诺转换为 已解决 承诺。不包括将 Promise 数组映射到超时 Promise 数组。

const timedOutValue = new Error( "promise timeout"); // unique value
const timedPromise = (promise, timeout) => {
    return new Promise( (resolve, reject) => {
      promise.then(resolve, reject);
      setTimeout( resolve, timeout, timedOutValue);  // could have used reject
    });
};

var pendingPromise = new Promise( (resolve,reject) => null);  // never settles 
timedPromise(pendingPromise)
.then(
    data=> console.log( "pendingPromise resolved with %s",
       data === timedOutValue ? "timedOutValue" : data),
    reason=> console.log( "pendingPromise rejected with %s",
       reason === timedOutValue ? "timedOutValue" : reason)
);
 

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2019-05-25
    • 2018-06-29
    • 2021-01-23
    • 2014-05-24
    • 2021-06-29
    • 2020-08-06
    • 2018-02-06
    相关资源
    最近更新 更多