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>>.
As we discussed in the comments - you have to wrap the resulting promise in a function or an object.
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));
})();
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() => {
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() => {
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);
})();
Alternatively, if your process is multi-step, each step could return a function (or an object with methods) for the next step:
(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);
})();
(In fact, that's very similar to using an async generator.)
These are all variations on a theme, but the take-away message is that a promise can never be fulfilled with another promise. You'll notice that the standard name for the functions you get from new Promise are resolve and reject, not fulfill and reject. That's because when you pass a promise into resolve (or return one from an async function, which is effetively the same thing), it resolves the promise to that other promise, rather than fulfilling the promise with that other promise.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With