简体   繁体   中英

How to write an asynchronous (promise-based) function that times out?

I want to have function which will do some work, if then is some condition true, then resolve promise. If not, wait little bit (lets say one second) and then try it again. Beside that if time limit expired, promise will reject. How can do I that? Look at my code example...If I wrap entire function in return new Promise(), I cant use await inside of it (which I need...I need to sleep some time if condition fail, and also I need wait to my await at end of my function).

Here is my example of code:

async funcWithTimeLimit(numberToDecrement, timeLimit){
    let sleep = undefined;
    let timeLimitTimeout = setTimeout(() => { 
        if (sleep)
            clearTimeout(sleep); 
        return Promise.reject("Time limit of " + (timeLimit/1000) +" secs expired"); //reject
    }, timeLimit);

    while(numberToDecrement > 0){
        for(let i = 10;(i > 0 && numberToDecrement > 0); i--){ //Do some work
            numberToDecrement--;
        }

        if(numberToDecrement > 0){
            await new Promise(resolve => sleep = setTimeout(resolve, 1000));
        }
    }

    clearTimeout(timeLimitTimeout);
    await new Promise((resolve, reject) => sleep = setTimeout(resolve, 500)); // Do something
    return ""; //resolve
}

NOTE: My biggest problem is - how can I write this function to be able to catch rejection (at place where I call funcWithTimeLimit() ) in case of time limit expiration?

There are two basic approaches. One is to repeatedly check the time and throw an exception once you hit the limit, the other is to race each await ed promise against a timeout . A third would require cooperation by the asynchronous tasks that you start, if they give you a way to cancel them in their interface, to simply offload the task of checking for timeout to them.

In neither of them you can reject the async function by throwing an exception from a setTimeout .

  1. Rather simple:

     function doSomething() { return new Promise((resolve, reject) => { setTimeout(resolve, 500)); // Do something } } async funcWithTimeLimit(numberToDecrement, timeLimit) { while (numberToDecrement > 0) { for (let i = 10; i > 0 && numberToDecrement > 0; i--) { if (Date.now() > timelimit) throw new TimeoutError("Exceeded limit"); // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ numberToDecrement--; // Do some work } if (numberToDecrement > 0) { if (Date.now() + 1000 > timelimit) throw new TimeoutError("Exceeded limit"); // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ await new Promise(resolve => setTimeout(resolve, 1000)); } } // but ineffective here: await doSomething(); return ""; }
  2. Racing:

     async funcWithTimeLimit(numberToDecrement, timeLimit) { const timeout = new Promise((resolve, reject) => { setTimeout(() => { reject(new TimeoutError("Exceeded limit")); }, timeLimit - Date.now()); }); timeout.catch(e => void e); // avoid unhandled promise rejection if not needed while (numberToDecrement > 0) { for (let i = 10; i > 0 && numberToDecrement > 0; i--) { // no guarding of synchronous loops numberToDecrement--; // Do some work } if (numberToDecrement > 0) { await Promise.race([ timeout, // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ new Promise(resolve => setTimeout(resolve, 1000)), ]); } } await Promise.race([ timeout, // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ doSomething(), ]); return ""; }

    Notice that the setTimeout keeps the event loop open if when the function already completed, we just ignore the rejected promise. Better, but more laborious, is to cancel the timeout when we don't need it any longer:

     async funcWithTimeLimit(numberToDecrement, timeLimit) { let timer; const timeout = new Promise((resolve, reject) => { timer = setTimeout(() => { reject(new TimeoutError("Exceeded limit")); }, timeLimit - Date.now()); }); try { // as before: while (numberToDecrement > 0) { for (let i = 10; i > 0 && numberToDecrement > 0; i--) { numberToDecrement--; // Do some work } if (numberToDecrement > 0) { await Promise.race([ timeout, new Promise(resolve => setTimeout(resolve, 1000)) ]); } } await Promise.race([ timeout, doSomething() ]); return ""; } finally { clearTimeout(timer); // ^^^^^^^^^^^^^^^^^^^^ } }
  3. With cooperation from the called function it's better of course:

     function delay(t, limit) { if (Date.now() + t > timelimit) throw new TimeoutError("Exceeded limit"); return new Promise(resolve => setTimeout(resolve, t)); } function doSomething(limit) { return new Promise((resolve, reject) => { const timeout = setTimeout(() => { xhr.cancel(); // custom API-dependent cancellation reject(new TimeoutError("Exceeded limit")); }, limit - Date.now()); const xhr = someApiCall(); // Do something xhr.onerror = err => { clearTimeout(timeout); reject(err); }; xhr.onload = res => { clearTimeout(timeout); resolve(res); }; } } async funcWithTimeLimit(numberToDecrement, timeLimit) { while (numberToDecrement > 0) { for (let i = 10; i > 0 && numberToDecrement > 0; i--) { numberToDecrement--; // Do some work } if (numberToDecrement > 0) { await delay(1000, timeLimit); // ^^^^^^^^^ } } await doSomething(timeLimit); // ^^^^^^^^^ return ""; }

You can (where applicable) and should (where sensible) combine these approaches of course.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM