【力扣】2637. 有时间限制的 Promise 对象

【力扣】2637.有时间限制的 Promise 对象

一、题目

请你编写一个函数,它接受一个异步函数 fn 和一个以毫秒为单位的时间 t。它应根据限时函数返回一个有 限时 效果的函数。函数 fn 接受提供给 限时 函数的参数。

限时 函数应遵循以下规则:

  • 如果 fnt 毫秒的时间限制内完成,限时 函数应返回结果。
  • 如果 fn 的执行超过时间限制,限时 函数应拒绝并返回字符串 "Time Limit Exceeded"

示例 1:

复制代码
输入:
fn = async (n) => { 
  await new Promise(res => setTimeout(res, 100)); 
  return n * n; 
}
inputs = [5]
t = 50
输出:{"rejected":"Time Limit Exceeded","time":50}
解释:
const limited = timeLimit(fn, t)
const start = performance.now()
let result;
try {
   const res = await limited(...inputs)
   result = {"resolved": res, "time": Math.floor(performance.now() - start)};
} catch (err) {
   result = {"rejected": err, "time": Math.floor(performance.now() - start)};
}
console.log(result) // 输出结果

提供的函数设置在 100ms 后执行完成,但是设置的超时时间为 50ms,所以在 t=50ms 时拒绝因为达到了超时时间。

示例 2:

复制代码
输入:
fn = async (n) => { 
  await new Promise(res => setTimeout(res, 100)); 
  return n * n; 
}
inputs = [5]
t = 150
输出:{"resolved":25,"time":100}
解释:
在 t=100ms 时执行 5*5=25 ,没有达到超时时间。

示例 3:

复制代码
输入:
fn = async (a, b) => { 
  await new Promise(res => setTimeout(res, 120)); 
  return a + b; 
}
inputs = [5,10]
t = 150
输出:{"resolved":15,"time":120}
解释:
在 t=120ms 时执行 5+10=15,没有达到超时时间。

示例 4:

复制代码
输入:
fn = async () => { 
  throw "Error";
}
inputs = []
t = 1000
输出:{"rejected":"Error","time":0}
解释:
此函数始终丢出 Error

提示:

  • 0 <= inputs.length <= 10
  • 0 <= t <= 1000
  • fn 返回一个 Promise 对象

二、解决方案

概述

这个问题要求你增强一个异步函数,以便它返回的 Promise 会在时间限制到期时自动拒绝。

时间限制的用例

长时间运行的过程

你可能遇到过一些代码会一遍又一遍地重复执行。一个常见的例子是将数据加载到缓存中并使其与数据源保持同步。

JavaScript 复制代码
async function repeatProcessIndefinitely() {
  while (true) {
    try {
      await someProcess();
    } catch (e) {
      console.error(e);
    }
  }
}

如果 someProcess 永远无法完成,循环将被冻结,什么都不会发生。强制someProcess抛出错误将取消进程的冻结状态。

需要考虑的重要问题是,即使 Promise 被拒绝,someProcess 中的代码仍然可以继续执行。因此,可能会有多个代码块并行执行。更好的解决方案可能是解决导致冻结的根本问题,或者实施适当的取消。考虑解决"设计可取消的函数"以实现真正的取消。

以下代码展示了要在一小时后强制 Promise someProcess() 被拒绝:

复制代码
const ONE_HOUR_IN_MS = 3600 * 1000;
const timeLimitedProcess = timeLimit(someProcess, ONE_HOUR_IN_MS);
通知用户失败

想象一下,用户请求下载一个文件,你期望下载时间应该在 10 秒内完成。如果下载时间太长,而不希望让用户等待,最好是放弃下载并向用户显示错误消息。

与第一个用例类似,这确实只应该作为最终的手段来执行。更好的方法可能是实现加载指示器和/或解决导致速度慢的潜在问题。

方法 1:在新 Promise 内部调用函数

我们可以创建一个新的 Promise,它在传递的函数解析或拒绝时立即解析。这本质上是模拟了传递的函数,而不对其产生任何影响。要满足要求,我们只需要添加一个 setTimeout,它可以提前强制拒绝 Promise。

JavaScript 复制代码
var timeLimit = function(fn, t) {
  return async function(...args) {
    return new Promise((resolve, reject) => {
      setTimeout(() => {
        reject("Time Limit Exceeded");
      }, t);
      fn(...args).then(resolve).catch(reject);
    })
  }
};

方法 2:处理清除 Timeout

在上面的示例中,如果函数的 Promise 在时间限制到期之前完成,拒绝逻辑仍然会在将来的某个时候被不必要地触发。这不会对函数的外部行为产生任何影响,因为 Promise 只能解析或拒绝一次。拒绝已解析的 Promise 不会产生任何效果。

然而,想象一下如果时间限制设置得非常长。这些代码块将不得不在 JavaScript 事件循环最终执行它们之前在内存中存储很长时间。这可能被视为内存泄漏,而专业的实现应该避免这种情况。不过,这并不是这个问题的要求。

为了实现这一点,我们可以利用 setTimeout 返回的整数 ID,这实际上是对要执行的代码块的引用。然后,你可以使用内置的 clearTimeout(id) 来中止代码的执行。

我们还可以利用 Promise.finally 方法。传递给它的回调将在 Promise 解析或拒绝时执行。

JavaScript 复制代码
var timeLimit = function(fn, t) {
  return async function(...args) {
    return new Promise((resolve, reject) => {
      const timeout = setTimeout(() => {
        reject("Time Limit Exceeded");
      }, t);
      fn(...args)
        .then(resolve)
        .catch(reject)
        .finally(() => clearTimeout(timeout));
    })
  }
};

方法 3:Promise Race

我们可以通过使用Promise.race函数来简化代码。它接受一个 Promise 数组并返回一个新的 Promise。返回的 Promise 将解析或拒绝其中一个 Promise 解析或拒绝的第一个值。

JavaScript 复制代码
var timeLimit = function(fn, t) {
  return async function(...args) {
    const timeLimitPromise = new Promise((resolve, reject) => {
      setTimeout(() => reject("Time Limit Exceeded"), t)
    });
    const returnedPromise = fn(...args);
    return Promise.race([timeLimitPromise, returnedPromise]);
  }
};

方法 4:Async/Await + 清除 Timeout

我们可以修改方法 2 以使用 async/await 语法。你只能在异步函数内使用 await 关键字,因此我们必须将async关键字添加到传递给新 Promise 的回调中。现在回调返回一个 Promise,而不是 undefined。请注意,这是可以接受的,因为 Promise构造函数不会查看回调返回的内容。不过,一个常见的错误是不恰当地将代码标记为异步。

JavaScript 复制代码
var timeLimit = function(fn, t) {
  return async function(...args) {
    return new Promise(async (resolve, reject) => {
      const timeout = setTimeout(() => {
        reject("Time Limit Exceeded");
      }, t);

      try {
        const result = await fn(...args);
        resolve(result);
      } catch(err) {
        reject(err);
      }
      clearTimeout(timeout);
    });
  };
};

结语

通过这些方法,你可以增强异步函数,使其在时间限制内自动拒绝 Promise。这在处理长时间运行的进程或通知用户失败等情况下非常有用。根据你的需要,选择适当的方法来实现时间限制。

相关推荐
Remember_9931 小时前
【数据结构】二叉树:从基础到应用全面解析
java·数据结构·b树·算法·leetcode·链表
zhengxianyi5152 小时前
Vue2 打包部署后通过修改配置文件修改全局变量——实时生效
前端·vue.js·前后端分离·数据大屏·ruoyi-vue-pro
灵犀坠2 小时前
Vue3 实现音乐播放器歌词功能:解析、匹配、滚动一站式教程
开发语言·前端·javascript·vue.js
north_eagle2 小时前
ReAct 框架详解
前端·react.js·前端框架
纟 冬2 小时前
React Native for OpenHarmony 实战:待办事项实现
javascript·react native·react.js
OEC小胖胖2 小时前
13|React Server Components(RSC)在仓库中的落点与边界
前端·react.js·前端框架·react·开源库
OEC小胖胖2 小时前
14|Hook 的实现视角:从 API 到 Fiber Update Queue 的连接点
前端·react.js·前端框架·react·开源库
i7i8i9com2 小时前
React 19学习基础-2 新特性
javascript·学习·react.js
Swift社区2 小时前
LeetCode 377 组合总和 Ⅳ
算法·leetcode·职场和发展