如何编写超时的异步基于承诺函数?

问题描述

我想要一个可以做一些工作的函数,如果某些条件为真,则解决承诺。如果没有,请稍等(让我们说一秒钟),然后再试一次。除此之外,如果时间限制到期,promise 将拒绝。我怎么能这样?看看我的代码示例...如果我将整个函数包装在 return new Promise() 中,我不能在其中使用 await(我需要...如果条件失败,我需要睡一段时间,我还需要等待我在函数结束时等待)。

这是我的代码示例:

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
}

注意:我最大的问题是 - 我如何编写这个函数以便在时间限制到期时能够捕获拒绝(在我调用 funcWithTimeLimit() 的地方)?

解决方法

有两种基本方法。一种是反复检查时间,一旦达到限制就抛出异常,另一种是race each awaited promise against a timeout。第三个将需要您启动的异步任务的合作,如果它们让您可以在其界面中取消它们,只需将检查超时的任务卸载给它们。

在这两种情况下,您都不能通过从 async function 抛出异常来拒绝 setTimeout

  1. 相当简单:

    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. 赛车:

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

    注意 setTimeout 保持事件循环打开,如果函数已经完成,我们只是忽略被拒绝的承诺。更好,但更费力的是,当我们不再需要时取消超时:

    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. 在被调用函数的配合下当然更好:

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

您当然可以(在适用的情况下)并且应该(在合理的情况下)将这些方法结合起来。

相关问答

Selenium Web驱动程序和Java。元素在(x,y)点处不可单击。其...
Python-如何使用点“。” 访问字典成员?
Java 字符串是不可变的。到底是什么意思?
Java中的“ final”关键字如何工作?(我仍然可以修改对象。...
“loop:”在Java代码中。这是什么,为什么要编译?
java.lang.ClassNotFoundException:sun.jdbc.odbc.JdbcOdbc...