Setting a timeout for each promise within a promise.all

只愿长相守 提交于 2020-01-12 19:11:30

问题


I am able to successfully perform a Promise.all, and gracefully handle resolves and rejects. However, some promises complete within a few milliseconds, some can/could take a while.

I want to be able to set a timeout for each promise within the Promise.all, so it can attempt to take a maximum of say 5seconds.

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

I have tried adding a timeout to createPromise(), however this does not seem to work. Setting a timeout to 300ms, some requests continue for 4+seconds:

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

I have access to Bluebird if it will makes things easier?


回答1:


Here's a scheme that creates a Promise.raceAll() function that works kind of like a combination of Promise.all() and Promise.race() where the promises all have a timeout time and value so that if the promise doesn't resolve before that time, it will be short circuited to resolve with the passed in value. This essentially puts each promise into a Promise.race() with a timer. If the timer wins, the promise is resolved with the default value. If the original promise wins, it's resolved with the actual promise result. We use Promise.race() to resolve with the first one to finish (the timeout or the original promise). This is a classic use for Promise.race() (in fact the only practical use I've ever really used it for).

A classic example would be to get me all the results you can in the next 15 seconds. Any results that take longer than 15 seconds, just return null for them and don't wait for them. Here's the code to make this concept work:

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

So, you use Promise.raceAll() like Promise.all() in that you pass it an array of promises, but you also pass it a timeoutTime and a timeoutVal. The timeoutTime is the how long to wait before timing out the promises. The timeoutVal is what to put in the results array for any promise that timed out (often it will be something like null that you can easily recognize as a non-real result).


I'm not sure I entirely what you are doing in your specific code, but here's your links code using the above:

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

Or, if you want to make sure Promise.raceAll() gets all results, even if some promises reject, you can add a .catch() handler to each promise:

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



回答2:


Your attempt just overwrites promise with the timeout promise, meaning that the HTTP promise will be completely ignored.

This is one of the relatively few places where you'd actually use new Promise*: Create a promise, resolve or reject it based on the HTTP call, and reject it (or resolve it, but that seems odd) on timeout:

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

(That uses map on the assumption that links is an array.)

Note that once resolve or reject has been called for a promise, subsequent calls to either are completely ignored (without error or warning). (If that weren't true, we'd want a flag above to track whether we'd settled the promise already.)


* (Unless you have a promise-enabled version of setTimeout. If you do, you'd use Promise.race like jfriend00 did in their answer.)




回答3:


One solution might be to convert each promise in an argument array for Promise.all into a timed out promise (say using .map). The timeout function could resolve or reject a supplied argument promise depending on application needs.

Using a unique value to resolve a promise after timeout allows it to be identified in the result array supplied to a Promise.all.then( handler)

This example looks out converting a promise into a resolved promise using a helper function timedPromise. Mapping a promise array to an array of timed out promises is not included.

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


来源:https://stackoverflow.com/questions/48577702/setting-a-timeout-for-each-promise-within-a-promise-all

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!