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

Time:05-11

I have the following code. My intention is to start a task (which involves a few await calls before it can actually start). When it finishes starting it, I need to update the UI to tell user that the task has started and is waiting for result. Result will come in later so I want to return another Promise so the app knows when to update the UI with returned result:

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));
})();

However the above code does not work. Somehow resultPromise is number (100 after resolved) instead of Promise<number>. TypeScript also recognize startSomethingAsync returning Promise<number> instead of Promise<Promise<number>>.

Why is this happening? Shouldn't the async method wrap another Promise outside of the returned Promise? How do I achieve what I am trying to do and is it a "good" pattern?


I even tried wrapping it by myself:

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;
}

The function still returns Promise<number> instead of Promise<Promise<number>>.

CodePudding user response:

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

No. If you return a promise from an async function, the promise created by calling the async function is just resolved to that promise (it waits for that other promise to settle and takes its fulfillment or rejection as its own; more on promise terminology on my blog here). It doesn't set up the promise to be fulfilled with a promise. A promise is never fulfilled with a promise (regardless of async functions).

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

If you want to use the promise from startSomethingAsync, simply don't await it:

(async() => {
    // resultPromise is number instead of Promise<number>
    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);
})();

Note that if startSomethingAsync has an asynchronous delay before it starts its work (which would be highly unusual), the "Task started" log will occur before that asynchronous delay occurs. If you don't want that, separate out that part from the function so you can get your "Task started" logging in the middle. For instance, you might have a separate function that does the initial bit, and then you pass anything from that into startSomethingAsync:

(async() => {
    // resultPromise is number instead of Promise<number>
    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);
})();

CodePudding user response:

All async functions implicitly return a promise. If you explicitly return a promise, it will not be wrapped.

If a promise resolves another promise, they nested promises are flattened -

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

  • Related