prs99
prs99

Reputation: 71

How to use await with async function without creating a Promise?

I know an async function returns a Promise, so I thought of replacing this code:

const hi = function (delay) {
        return new Promise((resolve, reject) => {
            setTimeout(() => {
                console.log('hi');
                resolve();
            },delay)
        });
    };

const bye = async () => {
    await hi(1000);
    await hi(1000);
    await hi(1000);
    await hi(1000);
    return 'bye';
};

bye().then((msg) => {
    console.log(msg);
});

with this code:

const hi = async (delay) => {
    setTimeout(() => {
        console.log('hi');
    },delay);
};

const bye = async () => {
    await hi(1000);
    await hi(1000);
    await hi(1000);
    await hi(1000);
    return 'bye';
};

bye().then((msg) => {
    console.log(msg);
});

The only difference is I'm using an async function instead of a function which returns a Promise, since an async function return a promise. But the second code doesn't work as I expected it too. It just console.logs bye immediately and then after 1s console.logs 'hi' 4 times. Can you please help me understand the reason behind this?

Upvotes: 1

Views: 6150

Answers (4)

3limin4t0r
3limin4t0r

Reputation: 21110

This answer adds some additional info to the one provided by Yousaf.

To shortly answer your comment:

Thanks for the explanation. Is there any way to write the hi function without using the 'Promise' keyword?

The answer is no. To convert a function using a callback interface to one that uses a promise you will have to manually create a promise somewhere. Functions using an older interface (usually) don't return promises, therefore you cannot await them. You can only await objects that are thenable (i.e. has a "then" method). If you await a non-thenable value or object, the await statement is essentially ignored.

MDN recommends wrapping callback methods at the lowest level possible:

Creating a Promise around an old callback API

A Promise can be created from scratch using its constructor. This should be needed only to wrap old APIs.

In an ideal world, all asynchronous functions would already return promises. Unfortunately, some APIs still expect success and/or failure callbacks to be passed in the old way. The most obvious example is the setTimeout() function:

setTimeout(() => saySomething("10 seconds passed"), 10*1000);

Mixing old-style callbacks and promises is problematic. If saySomething() fails or contains a programming error, nothing catches it. setTimeout is to blame for this.

Luckily we can wrap setTimeout in a promise. Best practice is to wrap problematic functions at the lowest possible level, and then never call them directly again:

const wait = ms => new Promise(resolve => setTimeout(resolve, ms));

wait(10*1000).then(() => saySomething("10 seconds")).catch(failureCallback);

Basically, the promise constructor takes an executor function that lets us resolve or reject a promise manually. Since setTimeout() doesn't really fail, we left out reject in this case.

If you would apply this info to your situation, this means wrapping the setTimeout() call within a Promise constructor like shown above.

// The only responsibility of this function is converting the old
// callback interface, to a promise interface.
function wait(ms) {
  return new Promise(resolve => setTimeout(resolve, ms));
}

Then use this new function in hi() instead of setTimeout().

async function hi(delay) {
  await wait(delay);
  console.log('hi');
}

Upvotes: 1

Yousaf
Yousaf

Reputation: 29282

One major difference in both code examples is related to the return value of hi function.

In the first code example, function hi returns a promise whereas in the second code example, function hi does not explicitly returns any value; as a result, the promise returned by the async function is implicitly resolved with the value of undefined without waiting for the setTimeout's callback function to execute.

A promise object doesn't makes anything asynchronous; its just a wrapper around an already asynchronous operation and the promise object notifies whether the asynchronous operation completed successfully or whether it failed.

In the first code example, you have a wrapper promise around the setTimeout. That wrapper promise is not fulfilled until resolve is called from within the callback function of the setTimeout.

In the second code example, the promise returned by the async function isn't automatically bound to the asynchronous code inside the function.

After calling setTimeout, code execution reaches the end of the hi function; As there is no explicit return statement, promise returned by the async function is fulfilled with the value of undefined. This happens before the callback function of the setTimeout is called.

Upvotes: 5

axiac
axiac

Reputation: 72186

A promise captures the result of an asynchronous processing that just started.
On the version of the code without promises, the asynchronous processing happens the same way as on the first version but there is nobody there waiting for it when it completes.

The second version of the hi() function is synchronous. It completes immediately. The async keyword does not have any effect on it. Also await hi() does not help, the function is still synchronous and it completes immediately.

Upvotes: 0

user16435030
user16435030

Reputation:

What's confusing you here is the setTimeout, your await hi( will actually await, but there isn't anything to wait for, that promise will almost immediately resolve and return so all 4 of them will execute almost instantaneously, then log "bye", then after 1 second your timeout will trigger and console.log "hi".

Upvotes: 1

Related Questions