【力扣】2637.有时间限制的 Promise 对象
一、题目
请你编写一个函数,它接受一个异步函数 fn 和一个以毫秒为单位的时间 t。它应根据限时函数返回一个有 限时 效果的函数。函数 fn 接受提供给 限时 函数的参数。
限时 函数应遵循以下规则:
- 如果
fn在t毫秒的时间限制内完成,限时 函数应返回结果。 - 如果
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 <= 100 <= t <= 1000fn返回一个 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。这在处理长时间运行的进程或通知用户失败等情况下非常有用。根据你的需要,选择适当的方法来实现时间限制。