如何在 Javascript 异步函数中 return Promise?为什么 Async 方法不包装 returned Promise?

How to return a Promise in a Javascript async function? Why does Async method not wrap the returned Promise?

我有以下代码。我的意图是开始一个任务(在它真正开始之前涉及一些 await 调用)。当它完成 starting 它时,我需要更新 UI 以告诉用户任务已经开始并正在等待结果。结果稍后会出现,所以我想 return 另一个 Promise 这样应用程序知道何时用 returned 结果更新 UI:

async function startSomethingAsync() {
    await new Promise(r => globalThis.setTimeout(r, 1000));
    console.log("Task started");

    const finishedPromise = new Promise<number>(r => {
        globalThis.setTimeout(() => r(100), 3000);
    });
    return finishedPromise;
}

(async() => {
    // resultPromise is number instead of Promise<number>
    const resultPromise = await startSomethingAsync();

    // My intention: update UI after starting the task
    console.log("Task started");

    // Then when the result is there, do something else
    // Currently this is not compiled because resultPromise is not a Promise
    resultPromise.then(r => console.log("Result arrived: " + r));
})();

但是上面的代码不起作用。不知何故 resultPromisenumber(解析后为 100)而不是 Promise<number>。 TypeScript 还识别 startSomethingAsync returning Promise<number> 而不是 Promise<Promise<number>>.

为什么会这样? async 方法不应该在 returned Promise 之外包装另一个 Promise 吗?我如何实现我想要做的事情,这是一个“好的”模式吗?


我什至试过自己包起来:

async function startSomethingAsync() {
    await new Promise(r => globalThis.setTimeout(r, 1000));
    console.log("Task started");

    const finishedPromise = new Promise<Promise<number>>(r1 => r1(new Promise<number>(r2 => {
        globalThis.setTimeout(() => r2(100), 3000);
    })));
    return finishedPromise;
}

函数仍然是 returns Promise<number> 而不是 Promise<Promise<number>>.

所有 async 函数都隐含地 return 一个承诺。如果您明确 return 一个承诺,它不会被包装。

如果一个 promise 解决了另一个 promise,它们的嵌套 promise 将被展平 -

(new Promise(r =>
  r(new Promise(r2 =>
    r2("hello")
  ))
))
.then(console.log).catch(console.error)

Why is this happening? Shouldn't the async method wrap another Promise outside of the returned Promise?

没有。如果您 return 来自 async 函数的承诺,通过调用 async 函数创建的承诺只是 解析为 该承诺(它等待其他承诺解决并将其实现或拒绝视为自己的承诺;更多关于我博客上的承诺术语 here)。它没有设置要用承诺来实现的承诺。承诺是 never 履行承诺(无论 async 功能)。

How do I achieve what I am trying to do and is it a "good" pattern?

如果您想使用 startSomethingAsync 中的承诺,只需不要 await 它:

(async() => {
    const resultPromise = startSomethingAsync();
    //                    ^−−− no `await`

    // My intention: update UI after starting the task
    console.log("Task started");

    // Wait until it's finished
    const r = await resultPromise;
    //        ^−−− *now* we `await` await it
    console.log("Result arrived: " + r);
})();

请注意,如果 startSomethingAsync 在开始工作之前有一个异步延迟(这很不寻常),“任务已启动”日志将在该异步延迟发生之前出现。如果你不想要那个,把那个部分从函数中分离出来,这样你就可以在中间记录你的“任务开始”。例如,您可能有一个单独的函数来执行初始位,然后将其中的任何内容传递给 startSomethingAsync:

(async() => {
    const resultPromise = startSomethingAsync(await theStuffBefore());
    //                                        ^^^^^

    // My intention: update UI after starting the task
    console.log("Task started");

    // Wait until it's finished
    const r = await resultPromise;
    //        ^−−− *now* we `await` await it
    console.log("Result arrived: " + r);
})();

或者,如果您的流程是 multi-step,每个步骤都可以 return 一个函数(或一个带有方法的对象)用于下一步:

(async() => {
    const nextStep = await startSomethingAsync();
    const resultPromise = nextStep(); // No `await`

    // My intention: update UI after starting the task
    console.log("Task started");

    // Wait until it's finished
    const r = await resultPromise;
    console.log("Result arrived: " + r);
})();

(事实上,这与使用异步生成器非常相似。)

这些都是同一个主题的变体,但 take-away 传达的信息是,一个承诺永远无法用另一个承诺来实现。您会注意到从 new Promise 获得的函数的标准名称是 resolvereject,而不是 fulfillreject。那是因为当你将一个 promise 传递给 resolve(或 return 一个来自 async 函数的 promise,这实际上是同一件事),它 解析 承诺 另一个承诺,而不是用另一个承诺履行承诺。

正如我们在评论中讨论的那样 - 您必须将生成的承诺包装在函数或对象中。

async function startSomethingAsync() {
  await new Promise(r => setTimeout(r, 1000));

  const finishedPromise = new Promise(r => {
    setTimeout(() => r(100), 3000);
  });
  return () => finishedPromise;
}

(async () => {
  const resultPromise = await startSomethingAsync();
  resultPromise().then((r) => console.log("Result arrived: " + r));
})();