Rod
Rod

Reputation: 15423

I'm not quite understanding this asynchronous nature

Given the results below, I was expecting working to appear between the begin and end lines.

Rule:

Is there a way to make it work without changing the app.js file?

app.js

const service = require('./service');

console.log('*********** begin ***********');

(async () => {
    const results = await service.init();
})();

console.log('*********** end ***********');

service.js

exports.init = () => {
    return new Promise((reject, resolve) => {
        setTimeout(() => {
            console.log('working...');
        }, 2000);
    });
};

results:

C:\code\practice\promise-exercise2>node index.js
*********** begin ***********
*********** end ***********
working...

Upvotes: 2

Views: 116

Answers (5)

jfriend00
jfriend00

Reputation: 707158

Is there a way to make it work without changing the app.js file?

In Javascript, you cannot make asynchronous results work synchronously. Can't do it. There are various tools such as promises and async/await that help you program with asynchronous operations, but the fundamental rule is that an asynchronous results is always going to be asynchronous. The only wait to run code after it is to hook into the asynchronous result and only execute that code when you get the notification that the async result is now done.

Within an async function, you can use await to write "synchronous looking" code for asynchronous operations, but it only "looks" synchronous and it only applies within that function, it's not really synchronous (see explanation below).

I was expecting working to appear between the begin and end lines.

The usual misunderstanding here is that an async function is NOT blocking. It doesn't wait until all await operations inside it are done and completed before returning.

Instead, it runs synchronously until the first await. At the point where it gets the first await, it returns a promise back to the caller and the code after that function call continues to run.

Sometime later, when the promise resolves that was awaited and no other Javascript is running at the time, then the function picks up where it left off and runs some more until the next await or until it returns. If it finds another await, then it again pauses execution and returns control back to the JS interpreter to run other events (non-blocking). If it gets to the end of the function block or encounters a return statement, then the promise that it returned earlier gets resolved.

So, in this code of yours:

const service = require('./service');

console.log('*********** begin ***********');

(async () => {
    const results = await service.init();
})();

console.log('*********** end ***********');

Here's the sequence of events:

  1. Load the service module synchronously.
  2. console.log('*********** begin ***********');
  3. Call the async IIFE function.
  4. That async function runs until it gets to the first await. service.init() runs and returns a promise which the await operation is going to wait for. At that point, that function returns a separate promise (which you are not using or paying attention to). All async functions return a promise.
  5. The code following that async function runs now and you get console.log('*********** end ***********');.
  6. Sometime later service.init() resolves the promise that it returned and the results variable is filled with the resolved value of that promise.

While async and await can be enourmously useful, they are mostly syntactic sugar that just makes programming easier. They can be transpiled into regular promise handling that uses .then() instead of await. For example, suppose you had this:

async function foo1() {
    const results = await service.init();
    console.log("got results", results);
    return results;
}

foo1().then(results => {
    console.log("all done now");
}).catch(err => {
    console.log(err);
});

That foo function could also be written like this:

function foo2() {
    try {
        return service.init().then(results => {
            console.log("got results", results);
            return results;
        });
     } catch(e) {
         return Promise.reject(e);
     }
}

foo2().then(results => {
    console.log("all done now");
}).catch(err => {
    console.log(err);
});

These two implementations behave identically. The try/catch in foo2() is something that the async function does automatically and is only useful if service.init() or anything else inside of foo2() might throw an exception synchronously.

Upvotes: 3

Jason Goemaat
Jason Goemaat

Reputation: 29194

Short answer is no. Your anonymous function is asynchronous so it returns a Promise so it isn't resolved by the time you get down to outputting end. You can put all calls in their own async function and await the result, but that requires await on your async function:

console.log('Top of script');

let closure = async () => {
  console.log('*********** begin ***********');

  let result =  await (async () => {
      const results = await service.init();
      return results;
  })();

  console.log('*********** end ***********');
  return result;
}

console.log('Promise not created yet')

let p = closure(); // creates promise

console.log('Promise created');

p.then(result => console.log('closure result:', result));

console.log('Bottom of script');

This outputs:

Top of script
Promise not created yet
*********** begin ***********
Promise created
Bottom of script
working...
*********** end ***********
closure result: Test result

So looking at this you can see the code in the async function starts running when you call closure(), then it makes an async call doing the timeout so the code follows through after printing 'begin' when it does the await and the outer script continues saying 'Promise created' and to 'Bottom of script'.

The p.then() call tells the promise what to do when it completes, so after the timeout is done, the async function continues after the await with logging 'end'. When it returns the result that complets the promise and you get 'closure result' printed.

NOTE

(reject, resolve) is reversed, it should be (resolve, reject). Also you should call resolve() in your timeout or the promise will never complete. I used this in my fiddle:

const service = {
    init: () => {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            console.log('working...');
            resolve('Test result');
        }, 2000);
    });
    }
};

Upvotes: 0

Jingshao Chen
Jingshao Chen

Reputation: 3485

Maybe this will help:

const service = require('./service');

console.log('*********** begin ***********');

(async () =>/*
             \
              \
               \
                \  Will be run in a later event loop cycle
                  ------------------------------------------------------> */ { const results = await service.init(); })();

console.log('*********** end ***********');

Upvotes: 0

user10093237
user10093237

Reputation:

Javascript is single thread and utilizing a so called event loop to implement the async.

You can find lots of resource by searching event loop on google and I believe they explain it much better than I am able to do in abstract concept. https://blog.carbonfive.com/2013/10/27/the-javascript-event-loop-explained/


So, back to your code in app.js

Node will run these two lines instantly.

const service = require('./service');
console.log('*********** begin ***********');

The problem happens when it comes to this async operation

(async () => {
    const results = await service.init();
})();

The async is processed immediately after the log above. However, it is the async instead of its content is parsed immediately. The callback function inside will be throw to the corresponding handler (you are calling a node function async so this handler will be your node process).

When the node process is free, it starts to check is there anything done from the event loop. You have no control on that but at least we are sure the node is busy now as there is a sync operation after, which is

console.log('*********** end ***********');

Ok, after all, all coded are ran and the node process becomes free now, he start to find is there anything done in the event loop and he will find there is a

const results = await service.init();

Then the node will work on this function and that's why you see

*********** begin ***********
*********** end ***********
working...

The solution is quite simple by using async/await as what you did. But it should be noted that you need to put all process that you want to control the async behavior into the same async block.

Upvotes: 1

CertainPerformance
CertainPerformance

Reputation: 370619

app.js is calling both console.log('begin') and console.log('end') synchronously, while the working is asynchronous. I suppose you could change it by changing service.js to print working synchronously, but that probably doesn't match your use case. So, it's not really possible without changing app.js.

If you wanted something like this, you would put the end inside the async function, after the await:

console.log('*********** begin ***********');
(async () => {
  const results = await service.init();
  console.log('*********** end ***********');
})();

Upvotes: 5

Related Questions