如何实现“;功能超时”;在Javascript中-不仅仅是'setTimeout'

How to implement a "function timeout" in Javascript - not just the 'setTimeout'

本文关键字:Javascript 不仅仅是 setTimeout 超时 何实现 实现 功能      更新时间:2023-09-26

如何在Javascript中实现超时,不是window.timeout,而是类似session timeoutsocket timeout的东西-基本上是"function timeout"

系统中允许经过的指定时间段在发生指定事件之前,除非另有指定事件首先发生;无论哪种情况,当任何一个事件都会发生。

具体来说,我想要一个javascript observing timer,它将观察函数的执行时间,如果达到或超过指定时间,observing timer将停止/通知正在执行的函数。

非常感谢您的帮助!非常感谢。

我不完全清楚你在问什么,但我认为Javascript不能按照你想要的方式工作,所以无法完成。例如,不能让常规函数调用持续到操作完成或某一时间段(以先到者为准)。这可以在javascript之外实现,并通过javascript公开(就像同步ajax调用一样),但不能在具有常规函数的纯javascript中实现。

与其他语言不同,Javascript是单线程的,因此在执行函数时,计时器永远不会执行(除了网络工作者,但他们的能力非常有限)。计时器只能在函数执行完毕时执行。因此,您甚至不能在同步函数和计时器之间共享进度变量,因此计时器无法"检查"函数的进度。

如果您的代码是完全独立的(没有访问任何全局变量,没有调用其他函数,也没有访问DOM),那么您可以在web工作程序中运行它(仅在较新的浏览器中可用),并在主线程中使用计时器。当web工作程序代码完成时,它会向主线程发送一条消息,其中包含它的结果。当主线程接收到该消息时,它会停止计时器。如果计时器在收到结果之前启动,它可能会杀死web工作人员。但是,您的代码将不得不接受网络工作者的限制。

Soemthing也可以用异步操作来完成(因为它们与Javascript的单线程性更好地配合),如下所示:

  1. 启动异步操作,如ajax调用或加载图像
  2. 使用setTimeout()启动计时器作为超时时间
  3. 如果计时器在异步操作完成之前触发,则停止异步操作(使用API取消它)
  4. 如果异步操作在计时器触发之前完成,则使用clearTimeout()取消计时器并继续

例如,以下是如何在加载图像时设置超时:

function loadImage(url, maxTime, data, fnSuccess, fnFail) {
    var img = new Image();
    var timer = setTimeout(function() {
        timer = null;
        fnFail(data, url);
    }, maxTime);
    img.onLoad = function() {
        if (timer) {
            clearTimeout(timer);
            fnSuccess(data, img);
        }
    }
    img.onAbort = img.onError = function() {
        clearTimeout(timer);
        fnFail(data, url);
    }
    img.src = url;
}

我的问题被标记为这个问题的副本,所以我想我会回答它,尽管原来的帖子已经九年了。

我花了一段时间才明白Javascript是单线程的(我仍然不确定我是否100%理解),但以下是我如何使用Promises和回调解决类似用例的。它主要基于本教程。

首先,我们定义了一个timeout函数来包装Promise:

const timeout = (prom, time, exception) => {
    let timer;
    return Promise.race([
        prom,
        new Promise((_r, rej) => timer = setTimeout(rej, time, exception))
    ]).finally(() => clearTimeout(timer));
}

这是我想暂停的承诺:

const someLongRunningFunction = async () => {
  ...
  return ...;
}

最后,我这样使用它。

const TIMEOUT = 2000;
const timeoutError = Symbol();
var value = "some default value";
try {
  value = await timeout(someLongRunningFunction(), TIMEOUT, timeoutError);
}
catch(e) {
  if (e === timeoutError) {
    console.log("Timeout");
  }
  else {
    console.log("Error: " + e);
  }
}
finally {
  return callback(value);
}

这将使用返回值someLongRunningFunction或超时时的默认值调用callback函数。您可以修改它以不同的方式处理超时(例如抛出错误)。

您可以在web工作者中执行代码。然后,您仍然能够在代码运行时处理超时事件。一旦网络工作者完成其工作,您就可以取消超时。一旦超时,您就可以终止web工作程序。

execWithTimeout(function() {
    if (Math.random() < 0.5) {
        for(;;) {}
    } else {
        return 12;
    }
}, 3000, function(err, result) {
    if (err) {
        console.log('Error: ' + err.message);
    } else {
        console.log('Result: ' + result);
    }
});
function execWithTimeout(code, timeout, callback) {
    var worker = new Worker('data:text/javascript;base64,' + btoa('self.postMessage((' + String(code) + ''n)());'));
    var id = setTimeout(function() {
        worker.terminate();
        callback(new Error('Timeout'));
    }, timeout);
    worker.addEventListener('error', function(e) {
        clearTimeout(id);
        callback(e);
    });
    worker.addEventListener('message', function(e) {
        clearTimeout(id);
        callback(null, e.data);
    });
}

我意识到这是一个老问题/线程,但也许这对其他人会有所帮助。

这里有一个通用的callWithTimeout,你可以await:

export function callWithTimeout(func, timeout) {
  return new Promise((resolve, reject) => {
    const timer = setTimeout(() => reject(new Error("timeout")), timeout)
    func().then(
      response => resolve(response),
      err => reject(new Error(err))
    ).finally(() => clearTimeout(timer))
  })
}

测试/示例:

export function sleep(ms) {
  return new Promise(resolve => setTimeout(resolve, ms))
}
const func1 = async () => {
  // test: func completes in time
  await sleep(100)
}
const func2 = async () => {
  // test: func does not complete in time
  await sleep(300)
}
const func3 = async () => {
  // test: func throws exception before timeout
  await sleep(100)
  throw new Error("exception in func")
}
const func4 = async () => {
  // test: func would have thrown exception but timeout occurred first
  await sleep(300)
  throw new Error("exception in func")
}

呼叫:

try {
  await callWithTimeout(func, 200)
  console.log("finished in time")
}
catch (err) {
  console.log(err.message)  // can be "timeout" or exception thrown by `func`
}

你只需要使用一些核心技巧就可以实现这一点。例如,如果你知道函数返回什么样的变量(注意EVERYjs函数返回一些东西,默认为undefined),你可以尝试这样的方法:定义变量

var x = null;

并在单独的"线程"中运行测试:

function test(){
    if (x || x == undefined)
        console.log("Cool, my function finished the job!");
    else
        console.log("Ehh, still far from finishing!");
}
setTimeout(test, 10000);

并最终运行功能:

x = myFunction(myArguments);

只有当您知道函数不返回任何值(即返回的值是undefined),或者它返回的值总是"非false",即未转换为false语句(如0null等)时,这才有效。

这是我的答案,它从本质上简化了Martin的答案,并基于相同的教程。

承诺的超时包装器:

const timeout = (prom, time) => { 
    const timeoutError = new Error(`execution time has exceeded the allowed time frame of ${time} ms`);
    let timer; // will receive the setTimeout defined from time 
    timeoutError.name = "TimeoutErr";
    return Promise.race([
        prom,
        new Promise((_r, rej) => timer = setTimeout(rej, time, timeoutError)) // returns the defined timeoutError in case of rejection
    ]).catch(err => { // handle errors that may occur during the promise race
        throw(err);
    }) .finally(() => clearTimeout(timer)); // clears timer 
}

用于测试目的的承诺:

const fn = async (a) => { // resolves in 500 ms or throw an error if a == true
    if (a == true) throw new Error('test error');
    await new Promise((res) => setTimeout(res, 500));
    return "p2";
}

现在这里有一个测试功能:

async function test() {
    let result;
    try {  // finishes before the timeout
        result = await timeout(fn(), 1000); // timeouts in 1000 ms 
        console.log('• Returned Value :', result, ''n'); // result = p2
    
    } catch(err) {
        console.log('• Captured exception 0 : 'n ', err, ''n');
    }
    try { // don't finish before the timeout
        result = await timeout(fn(), 100); // timeouts in 100 ms
        console.log(result); // not executed as the timeout error was triggered
    } catch (err) { 
        console.log('• Captured exception 1 : 'n ', err, ''n');
    }
    try { // an error occured during fn execution time
        result = await timeout(fn(true), 100); // fn will throw an error
        console.log(result); // not executed as an error occured 
    } catch (err) {
        console.log('• Captured exception 2 : 'n ', err, ''n');
    }
     
} 

将产生以下输出:

•返回值:p2•捕获的异常1:TimeoutErr:执行时间已超过允许的100毫秒时间范围在C:''。。。''test promise race''test.js:34在异步测试中(C:''…''test promise race''test.js:63:18)•捕获的异常2:错误:测试错误在fn(C:''…''test promise race''test.js:45:26)测试时(C:''…''test promise race''test.js:72:32)

如果您不想在test函数中使用try ... catch指令,您也可以用return替换超时承诺包装器的catch部分中的throw指令。

通过这样做,result变量将接收否则抛出的错误

然后您可以使用它来检测result变量是否真的包含错误。

if (result instanceof Error) {
    // there was an error during execution
}
else {
    // result contains the value returned by fn
}

如果要检查错误是否与定义的超时有关,则必须检查"TimeoutErr"error.name值。

observing timerexecuting function之间共享一个变量。

window.setTimeoutwindow.setInterval实现observing timer。当observing timer执行时,它会为共享变量设置出口值。

executing function不断检查变量值。。如果指定了退出值,则返回。

相关文章: