为什么使用 Promise.finally 会导致未捕获的异常?
Why is this use of Promise.finally leading to uncaught exception?
我正在实现一个名为 timeout
的函数,以将承诺转换为如果未在最短时间内解决则将拒绝的承诺。
通过以下实现,当传递给 timeout
的承诺在分配的时间之前被拒绝时,我在 Chrome devtools 中收到一条 uncaught (in promise)
消息。
function timeout(promise, duration, message) {
const timeoutPromise = new Promise((_, reject) => {
const handle = setTimeout(() => {
reject(new TimeoutError(message ?? "Operation timed out."))
}, duration)
promise.finally(() => clearTimeout(handle)) //problem line
})
return Promise.race([promise, timeoutPromise])
}
不过,如果我将其更改为以下内容,我似乎没有问题。
function timeout(promise, duration, message) {
const timeoutPromise = new Promise((_, reject) => {
const handle = setTimeout(() => {
reject(new TimeoutError(message ?? "Operation timed out."))
}, duration)
const clear = () => clearTimeout(handle)
promise.then(clear).catch(clear)
})
return Promise.race([promise, timeoutPromise])
}
我想了解在第二种情况下调用 then
& catch
与在第一种情况下调用 finally
有何不同。
这是因为 finally
处理程序不同于 then
和 catch
处理程序。如果承诺被拒绝,finally
处理程序无法将拒绝更改为解决方案。就像 try
/catch
/finally
结构的 finally
块一样,它在很大程度上是透明的。
我在新书(第 8 章)中是这样描述的:
In the normal case, a finally
handler has no effect on the fulfillment or rejection passing through
it (like a finally
block): any value it returns other than a thenable that is/gets rejected is
ignored. But if it throws an error or returns a thenable that rejects, that error/rejection supersedes
any fulfillment or rejection passing through it (just like throw in a finally
block). So it can't change
a fulfillment value — not even if it returns a different value — but it can change a rejection reason into a
different rejection reason, it can change a fulfillment into a rejection, and it can delay a fulfillment (by
returning a thenable that is ultimately fulfilled).
Or to put it another way: it's like a finally
block that can't contain a return
statement. (It might
have a throw
or might call a function or do some operation that throws, but it can't return a new value.
这是无法将拒绝转化为满足的示例:
Promise.reject(new Error("Failed"))
.finally(() => {
console.log("finally handler returns null");
return null;
});
Look in the browser's real console to see the unhandled rejection error.
在您的代码中处理它的另一种方法是用这个代替问题行:
promise.catch(() => {}).finally(() => clearTimeout(handle))
或
promise.catch(() => {}).then(() => clearTimeout(handle))
在 finally
(或 then
)处理程序之前将拒绝转换为完成(仅在此分支上)。
我正在实现一个名为 timeout
的函数,以将承诺转换为如果未在最短时间内解决则将拒绝的承诺。
通过以下实现,当传递给 timeout
的承诺在分配的时间之前被拒绝时,我在 Chrome devtools 中收到一条 uncaught (in promise)
消息。
function timeout(promise, duration, message) {
const timeoutPromise = new Promise((_, reject) => {
const handle = setTimeout(() => {
reject(new TimeoutError(message ?? "Operation timed out."))
}, duration)
promise.finally(() => clearTimeout(handle)) //problem line
})
return Promise.race([promise, timeoutPromise])
}
不过,如果我将其更改为以下内容,我似乎没有问题。
function timeout(promise, duration, message) {
const timeoutPromise = new Promise((_, reject) => {
const handle = setTimeout(() => {
reject(new TimeoutError(message ?? "Operation timed out."))
}, duration)
const clear = () => clearTimeout(handle)
promise.then(clear).catch(clear)
})
return Promise.race([promise, timeoutPromise])
}
我想了解在第二种情况下调用 then
& catch
与在第一种情况下调用 finally
有何不同。
这是因为 finally
处理程序不同于 then
和 catch
处理程序。如果承诺被拒绝,finally
处理程序无法将拒绝更改为解决方案。就像 try
/catch
/finally
结构的 finally
块一样,它在很大程度上是透明的。
我在新书(第 8 章)中是这样描述的:
In the normal case, a
finally
handler has no effect on the fulfillment or rejection passing through it (like afinally
block): any value it returns other than a thenable that is/gets rejected is ignored. But if it throws an error or returns a thenable that rejects, that error/rejection supersedes any fulfillment or rejection passing through it (just like throw in afinally
block). So it can't change a fulfillment value — not even if it returns a different value — but it can change a rejection reason into a different rejection reason, it can change a fulfillment into a rejection, and it can delay a fulfillment (by returning a thenable that is ultimately fulfilled).Or to put it another way: it's like a
finally
block that can't contain areturn
statement. (It might have athrow
or might call a function or do some operation that throws, but it can't return a new value.
这是无法将拒绝转化为满足的示例:
Promise.reject(new Error("Failed"))
.finally(() => {
console.log("finally handler returns null");
return null;
});
Look in the browser's real console to see the unhandled rejection error.
在您的代码中处理它的另一种方法是用这个代替问题行:
promise.catch(() => {}).finally(() => clearTimeout(handle))
或
promise.catch(() => {}).then(() => clearTimeout(handle))
在 finally
(或 then
)处理程序之前将拒绝转换为完成(仅在此分支上)。