Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Setting a timeout for each promise within a promise.all

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?

like image 284
Oam Psy Avatar asked Feb 02 '18 07:02

Oam Psy


People also ask

How do you set a timeout for a promise?

Many times in the past I've found myself needing to add a timeout to a promise in JavaScript. setTimeout() is not exactly a perfect tool for the job, but it's easy enough to wrap it into a promise: const awaitTimeout = delay => new Promise(resolve => setTimeout(resolve, delay)); awaitTimeout(300). then(() => console.

Does promise all wait for all Promises?

Using Promise.all()Promise.all waits for all fulfillments (or the first rejection).

Do Promises have timeouts?

Promises in Javascript has no concept of time. then() , it will wait until the Promise is either resolved or rejected. This is usually not a problem as most async tasks finish within a reasonable time and their result is needed.

What if one promise fails in promise all?

Rejection: If any of the passed promises are rejected, then this method rejects the value of that promise, whether or not the other promises have resolved. In other words, if any promise fails to get executed, then Promise.


2 Answers

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

like image 142
T.J. Crowder Avatar answered Nov 15 '22 23:11

T.J. Crowder


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
});
like image 38
jfriend00 Avatar answered Nov 15 '22 22:11

jfriend00