2

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...
Rod
  • 14,529
  • 31
  • 118
  • 230

5 Answers5

5

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 ***********');
})();
CertainPerformance
  • 356,069
  • 52
  • 309
  • 320
3

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.

jfriend00
  • 683,504
  • 96
  • 985
  • 979
  • So basically async without await is synchronous? I thought that it spawns something like `setTimeout(function(){},0)` – bigless Jul 18 '18 at 00:35
  • @bigless - Yes, it is. Without `await`, it would just be a function call that runs and then returns a promise. Note, I added more explanation to my answer. – jfriend00 Jul 18 '18 at 00:37
  • @bigless - I added a `.then()` analogy so you can see what `async/await` is doing under the covers. – jfriend00 Jul 18 '18 at 00:47
  • @bigless - Perhaps what you're thinking of is that `.then()` is always forced to the next tick of the event loop and is never called synchronously. It's handlers are always called with nothing but platform code on the stack. – jfriend00 Jul 18 '18 at 00:48
  • No. My though was that anything in asynchronous function can be blocking and await blocks until Promise is resolved/rejected. From some reason, I always used pure Promises. Maybe cos I like self-describing code and `.then` can be overlooked rarely + it has clear flow and catch. It was big surprise for me that async is in-fact synchronous. Thanks for such post. – bigless Jul 18 '18 at 01:07
  • @bigless - Yeah, what confuses people is that `await` blocks WITHIN the function (giving it a synchronous feel within the function), but not at a wider scale outside the function. So, as soon as you hit the first `await`, the function actually returns its promise at that point so other execution can then continue. It's actually a pretty weird concept (semi-blocking), but it is very useful. You can often write code a lot simpler using `await` (particular with `for` loops) than you can with only `.then()`. – jfriend00 Jul 18 '18 at 02:22
  • @bigless - You are correct to worry about proper error handling because it's easier to forget that promises can reject when using `async/await` and somebody needs to handle a rejection. – jfriend00 Jul 18 '18 at 02:23
  • You mentioned for loops. Is ok to block await for user defined time? For this purpose I also exposed resolve function. I ran into these waters in [here](https://stackoverflow.com/a/51412662/4209136), but I am not sure if this is proper solution for that case. Just checking validity of async/await related answer as a newbie. Thanks! – bigless Jul 19 '18 at 00:48
  • @bigless - It's OK to use promises with `await` that resolve in whatever time makes sense for your app. The event loop and the ability to process other events is not blocked when doing that, only the execution of the local function is temporarily blocked. Without seeing the specific code you're trying to ask about, I don't really know how to respond with more than that. – jfriend00 Jul 19 '18 at 01:12
1

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.

0

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 ***********');
Jingshao Chen
  • 3,405
  • 2
  • 26
  • 34
0

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);
    });
    }
};
Jason Goemaat
  • 28,692
  • 15
  • 86
  • 113