【问题标题】:What is the equivalent of not calling a callback when inside an async function?在异步函数中不调用回调的等价物是什么?
【发布时间】:2018-03-03 00:14:26
【问题描述】:

在 async/await 之前,当我的代码使用回调时,我能够做三件事:(1) 使用结果调用回调,(2) 使用错误调用回调,或 (3) 不调用回调完全没有。

案例 (3) 用于以下情况:假设您有一个缩放按钮,用户可以单击它以更高分辨率渲染图像,这是一个异步过程。如果用户再次单击缩放按钮,则第一次渲染不再相关,可以取消以让新的缩放级别渲染运行。我通过从函数内部返回而不调用回调来处理这个问题,例如

if (process.wasCanceled()) {
  return;
}
// ...
callback(someResult);

使用 async/await,您只能做两件事:返回或抛出。目前,我一直在使用 throw 来指示操作已取消,因为返回可能会错误地指示上游进程应该继续运行。但是抛出的问题是所有上游调用者都需要知道它本身并不是一个真正的错误,因此他们可能需要检查错误的类型。

我的另一个疯狂想法是创造一个永不回报的承诺。例如。 await never(),其中函数定义如下:

async function never () {
    return new Promise(function () {});
}

这相当于不调用回调。

但我不知道这是否会一遍又一遍地泄漏内存。

如果没有我上面提到的缺点,有没有更好的等价物?

【问题讨论】:

  • 我建议将 throw 与特定消息一起使用,例如“已取消”。是的,上游的一切都必须寻找这个......但这不是你想要的吗?
  • 这是一个非常有趣的问题。我确实相信never() 出于内存泄漏原因是一个非常糟糕的主意。 @ControlAltDel 在我看来,他想要一个上游不必必须查看的情况,因为在他的第一个示例中,如果process.wasCanceled(),回调就不会运行。跨度>
  • 当我更多地考虑await never() 时,我认为它可能还可以。这两个问题(stackoverflow.com/questions/20068467stackoverflow.com/questions/38286358)似乎表明浏览器可能会垃圾收集由never 函数创建的promise,因为promise 的函数中没有任何东西将它连接到堆的其余部分。所以也许它不会泄漏。
  • 我没有看到抛出取消错误的问题。您的所有上游调用者都应该传播而无需您做任何事情,除了需要知道操作已取消的最顶层。
  • @ControlAltDel 我遗漏了架构的关键部分,即上游调用者创建 Process 对象并将其传递给被调用者。因此,可以取消被调用者的唯一方法是调用者(或其调用者之一)发起取消。所以呼叫者已经决定取消 - 它只是不知道。然而,这确实忽略了任何可能需要清理进程的中间函数,所以你说得很好。

标签: javascript callback async-await cancellation


【解决方案1】:

如果绝对必要,您可以等待一个永不返回的承诺。这相当于不调用回调。

async function never () {
    return new Promise(function () {});
}

async function op (process) {
    // ...
    if (process.wasCanceled()) await never();
    // ...
}

根据这些答案,这将被垃圾回收,因为返回的 Promise 从未使用过,并且与 Promise 的函数参数内的堆没有任何连接。

Do never resolved promises cause memory leak?

Are JavaScript forever-pending promises bad?

但是,这很可能不是您想要做的,因为上游调用者可能想知道他们的操作已被取消。如果操作是由用户通过 UI 发起的,那么是的,在不告诉调用者的情况下取消可能是可以的,但如果操作是通过编程方式发起并以其他方式取消的,例如由用户调用,那么调用代码可能需要知道这一点,以便它可以重试或清理资源。

因此,解决方案是抛出特定类的错误,以便调用者可以检测到进程已取消。例如

class ProcessCanceledError extends Error {
    ...
}

async function render (process) {
    while (...) {
        // do some rendering
        await delay(20);
        if (process.wasCanceled()) throw new ProcessCanceledError();
    }
}

var zoomProcess;

async function zoom () {
    let process = new Process();
    if (zoomProcess != null && !zoomProcess.isDone()) {
        zoomProcess.cancel();
    }
    try {
        await render();
    } catch (e) {
        // or you could do e.process === process
        if (e instanceof ProcessCanceledError &&
            process.wasCanceled() // make sure it was actually ours
        ) {
            // this assumes we are a top level function
            // otherwise, you would want to propagate the error to caller's caller
            return;
        }
        throw e;
    }
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-11-27
    • 1970-01-01
    • 2017-10-12
    • 2022-10-12
    • 2018-08-21
    • 1970-01-01
    相关资源
    最近更新 更多