Reputation: 15423
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
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:
console.log('*********** begin ***********');
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.console.log('*********** end ***********');
.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
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
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
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
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