I try to understand how JS processes asynchronous methods and finally I have come to async/await. Trying to get a full insight, I have created this example:
async function first() {
console.log(9);
await Promise.resolve(2).then((r) => console.log(r));
console.log(0);
await Promise.resolve(3).then((r) => console.log(r));
}
async function second() {
console.log(10);
await Promise.resolve(4).then((r) => console.log(r));
console.log(11);
await Promise.resolve(5).then((r) => console.log(r));
}
first();
second();
const promise = Promise.resolve("new Promise");
promise.then((str) => console.log(str));
//The output:
//9
//10
//2
//4
//new Promise
//0
//11
//3
//5
So, I have a question, why does it have such an order, and how JS's EventLoop works with async/await
I tried to create some other examples with similar syntax but the result is the same
Here is a simplified time table of the most important evaluated expressions, the callstack at that time, the promise job queue (with promise reactions):
Callstack
Evaluation
Job Queue
Script
first()
-
Script>first
console.log(9)
-
Script>first
Promise.resolve(2).then()
(r=2)=>console.log(r)
Script>first
await <pending>
(r=2)=>console.log(r)
Script
second()
(r=2)=>console.log(r)
Script>second
console.log(10)
(r=2)=>console.log(r)
Script>second
Promise.resolve(4).then()
(r=2)=>console.log(r)(r=4)=>console.log(r)
Script>second
await <pending>
(r=2)=>console.log(r)(r=4)=>console.log(r)
Script
promise = Promise.resolve("new Promise")
(r=2)=>console.log(r)(r=4)=>console.log(r)
Script
promise.then((str)=>console.log(str))
(r=2)=>console.log(r)(r=4)=>console.log(r)(str="new Promise")=> console.log(str)
Job
(r=2)=>console.log(r)
(r=4)=>console.log(r)(str="new Promise")=> console.log(str)
Job>anonym
console.log(2)
(r=4)=>console.log(r)(str="new Promise")=> console.log(str)resume first()
Job
(r=4)=>console.log(r)
(str="new Promise")=> console.log(str)resume first()
Job>anonym
console.log(4)
(str="new Promise")=> console.log(str)resume first()resume second()
Job
(str="new Promise")=> console.log(str)
resume first()resume second()
Job>anonym
console.log("new Promise")
resume first()resume second()
Job
resume first()
resume second()
Job>first
console.log(0)
resume second()
Job>first
Promise.resolve(3).then()
resume second()(r=3)=>console.log(r)
Job>first
await <pending>
resume second()(r=3)=>console.log(r)
Job
resume second()
(r=3)=>console.log(r)
Job>second
console.log(11)
(r=3)=>console.log(r)
Job>second
Promise.resolve(5).then()
(r=0)=>console.log(r)(r=5)=>console.log(r)
Job>second
await <pending>
(r=3)=>console.log(r)(r=5)=>console.log(r)
Job
(r=3)=>console.log(r)
(r=5)=>console.log(r)
Job>anonym
console.log(3)
(r=5)=>console.log(r)resume first()
Job
(r=5)=>console.log(r)
resume first()
Job>anonym
console.log(5)
resume first()resume second()
Job
resum first()
resume second()
Job>first
-
resume second()
Job
resume second()
-
Job>second
-
-
Some points to highlight:
When a then method is executed on a promise that is in a fulfilled state, a job is added to a job queue. When the script has been executed to completion the first job in the promise job queue is extracted and executed.
Be aware that when a then method is executed this creates a new promise that is pending, even when then is called on a resolved promise. That pending promise will only resolve when the callback passed as argument has been executed, and this happens via a job (so, asynchronously).
After the expression following an await is executed, the async function's running state is saved, and the function returns. This running state will be restored by a job that is queued when the awaited promise resolves.
Hope this clarifies a few things.
This is:
async function first() {
console.log(9);
await Promise.resolve(2).then((r) => console.log(r));
console.log(0);
await Promise.resolve(3).then((r) => console.log(r));
}
Is the same as this:
function first() {
console.log(9);
return Promise.resolve(2).then((r) => console.log(r)).then(() => {
console.log(0);
return Promise.resolve(3).then((r) => console.log(r));
});
}
function first() {
console.log(9);
return Promise.resolve(2).then((r) => console.log(r)).then(() => {
console.log(0);
return Promise.resolve(3).then((r) => console.log(r));
})
}
function second() {
console.log(10);
return Promise.resolve(4).then((r) => console.log(r)).then(() => {
console.log(11);
Promise.resolve(5).then((r) => console.log(r));
})
}
first();
second();
const promise = Promise.resolve("new Promise");
promise.then((str) => console.log(str));
Same with better numbering
function first() {
console.log(1);
return Promise.resolve('a').then((r) => console.log(4, r)).then(() => {
console.log(7);
return Promise.resolve('b').then((r) => console.log(9, r));
})
}
function second() {
console.log(2);
return Promise.resolve('c').then((r) => console.log(5, r)).then(() => {
console.log(8);
Promise.resolve('d').then((r) => console.log(10, r));
})
}
first();
second();
console.log(3)
const promise = Promise.resolve('e');
promise.then((str) => console.log(6, str));
Related
I'm trying to understand why this piece of code doesn't behave as I expect:
async function test() {
await setTimeout(() => {
console.log('done')
}, 1000)
console.log('it finished');
}
test();
This first prints it finished and then prints done afterwards. Shouldn't this code wait for the timeout to finish before executing console.log('it finished'); or have I misunderstood something?
You can only usefully await a promise.
setTimeout returns a timeout id (a number) that you can pass to clearTimeout to cancel it. It doesn't return a promise.
You could wrap setTimeout in a promise…
async function test() {
await new Promise( resolve => setTimeout(() => {
console.log('done');
resolve("done");
}, 1000));
console.log('it finished');
}
test();
console.log('1');
await get()
.then()
.catch(() => {
console.log('2');
return;
});
console.log('3');
Why did console logs 1 2 3? I thought after return statement, the code will be not executed?
return will only terminate the current function. Here, the function that gets terminated by the return is the .catch callback.
Since .catch returns a resolved Promise, the awaited Promise chain will resolve when the catch resolves.
If you want to stop the outer function when the catch runs, have your catch return something that you check outside, eg:
(async() => {
console.log('1');
const result = await Promise.reject()
.catch(() => {
console.log('2');
return 2;
});
if (result === 2) {
return;
}
console.log('3');
})();
Or have the .catch throw an error, so that the outer Promise chain rejects. Since it's being awaited, the whole outer Promise will then reject:
const fn = async () => {
console.log('1');
const result = await Promise.reject()
.catch(() => {
console.log('2');
throw new Error();
});
console.log('3');
};
fn()
.then(() => console.log('resolved'))
.catch(() => console.log('rejected'))
If your .catch doesn't do anything substantial but you want this sort of behavior, it's usually a good idea to omit it entirely. That way, when there's an error, the awaited Promise will reject, the function will terminate, and the error can be caught by the appropriate consumer.
const fn = async () => {
console.log('1');
const result = await Promise.reject();
console.log('3');
};
fn()
.then(() => console.log('resolved'))
.catch(() => console.log('rejected'))
It's because the return is in a separate function (the catch handler).
The return only applies to the function it is inside of. It will not affect the outer scope.
First, I have to mention that I already look through many questions in stackoverflow, but many doesn't answer my question. Not to mention many doesn't even have an answer.
How do I achieve the following, making sure functionB() executes after functionA() finishes?
Note: I do not want to convert my async functions to new Promise(resolve=>{...})
because I'll have to convert the someServiceThatMakesHTTPCall() as well, and any other async functions within the call stack, which is a big change.
function functionThatCannotHaveAsyncKeyword() {
functionA()
.then(async function() {
await functionB();
})
.then(function() {
console.log('last');
});
}
async function functionA() {
console.log('first');
await someServiceThatMakesHTTPCall();
}
async function functionB() {
console.log('second');
await someServiceThatMakesHTTPCall();
}
Your approach using await in an async then callback will work, but it's unnecessarily complex if all you want to do is call the async function and have its result propagate through the chain. But if you are doing other things and want the syntax benefit of async functions, that's fine. I'll come back to that in a moment.
async functions returns promises, so you just return the result of calling your function:
function functionThatCannotHaveAsyncKeyword() {
functionA()
.then(function() {
return functionB(someArgument);
})
.then(function() {
console.log('last');
}); // <=== Note: You need a `catch` here, or this function needs
// to return the promise chain to its caller so its caller can
// handle errors
}
If you want to pass functionA's resolution value into functionB, you can do it even more directly:
functionA()
.then(functionB)
// ...
When you return a promise from a then callback, the promise created by the call to then is resolved to the promise you return: it will wait for that other promise to settle, then settle the same way.
Example:
const wait = (duration, ...args) => new Promise(resolve => {
setTimeout(resolve, duration, ...args);
});
async function functionA() {
await wait(500);
return 42;
}
async function functionB() {
await wait(200);
return "answer";
}
functionB()
.then(result => {
console.log(result); // "answer"
return functionA();
})
.then(result => {
console.log(result); // 42
})
.catch(error => {
// ...handle error...
});
Coming back to your approach using an async then callback: That works too, and makes sense when you're doing more stuff:
const wait = (duration, ...args) => new Promise(resolve => {
setTimeout(resolve, duration, ...args);
});
async function functionA() {
await wait(500);
return 42;
}
async function functionB() {
await wait(200);
return "answer";
}
functionB()
.then(async (result) => {
console.log(result); // "answer"
const v = await functionA();
if (v < 60) {
console.log("Waiting 400ms...");
await wait(400);
console.log("Done waiting");
}
console.log(v); // 42
})
.catch(error => {
// ...handle error...
});
You can use promise inside the first method as
function functionThatCannotHaveAsyncKeyword() {
return new Promise(async(resolve, reject)=> {
await functionA();
await functionB();
console.log('last');
resolve();
});
}
async function functionA() {
console.log('first');
await someServiceThatMakesHTTPCall();
}
async function functionB() {
console.log('second');
await someServiceThatMakesHTTPCall();
}
if someServiceThatMakesHTTPCall is async you can avoid all that by doing the following:
function functionThatCannotHaveAsyncKeyword() {
functionA()
.then(function() {
return functionB()
})
.then(function() {
console.log('last');
});
}
function functionA() {
console.log('first');
return someServiceThatMakesHTTPCall();
}
function functionB() {
console.log('second');
return someServiceThatMakesHTTPCall();
}
I'm trying to understand JavaScript async/await. How can I rewrite the below such that the output is "Hi" then "Bye" instead of "Bye" then "Hi":
JSFiddle
sayHi()
.then(sayBye);
async function sayHi() {
await setTimeout(function() {
$("#myOutput").text('hi');
}, 1000);
}
async function sayBye() {
$("#myOutput").text('bye');
}
In order to await setTimeout it needs to be wrapped into Promise. Then with async/await you can flatten your code write it without Promise then API:
(async () => { // await has to be inside async function, anonymous in this case
await sayHi()
sayBye()
})()
async function sayHi() {
return new Promise(function (resolve) {
$("#myOutput").text('hi');
setTimeout(function() {
resolve()
}, 1000)
});
}
async function sayBye() {
$("#myOutput").text('bye');
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="myOutput"></div>
setTimeout doesn't return a Promise. Create a helper function to wrap it in a Promise and then you can await it.
function delay(fn, t) {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(fn());
}, t);
});
}
sayHi()
.then(sayBye);
async function sayHi() {
await delay(() => {
//$("#myOutput").text('hi');
console.log("Hi");
}, 1000);
}
async function sayBye() {
//$("#myOutput").text('bye');
console.log("Bye");
}
Use the Promise way
sayHi()
.then(sayBye);
function sayHi() {
return new Promise(resolve => {
setTimeout(()=> {
$("#myOutput").text('hi'), resolve()
}, 1000);
})
}
async function sayBye() {
$("#myOutput").text('bye');
}
or the sayHi like this:
async function sayHi() {
await new Promise(resolve => {
setTimeout(()=> {
$("#myOutput").text('hi'), resolve()
}, 1000)
})
}
Using async/await is an excellent way to build acynchronous code in a quite controllable way. Promise based async function goes into microtasks depository, which event loop executes before events and methods contained in ordinary DOM refresh/web API depository (setTimeout() for example). However some versions of Opera and Firefox browsers set priority to setTimeout() over microtasks depository. Anyway, you can control order of execution if you combine Promise based function with async/await enabled function. For example:
// create function that returns Promise
let hi = () => {
return new Promise((resolve, reject) => {
setTimeout(_ => {
resolve('Hi '); // after 1500ms function is resolved and returns 'Hi '
}, 1500);
});
}
// create ordinary function that will return 'bye'
let sayBye = () => {
return 'Bye';
}
// create third function that will be async 'enabled',
// so it can use await keyword before Promise based functions
let sayHi = async () => {
let first = await hi(); // we store 'Hi ' into 'first' variable
console.log(first);
let second = sayBye(); // this execution will await until hi() is finished
console.log(second);
}
// execute async enabled function
sayHi();
We can add try / catch block inside sayHi() function for controlling error on promise reject(), but this is out of scope of your question.
Have a nice day!
You cannot use async/await for functions that are not returning Promise
When an async function is called, it returns a Promise. When the async function returns a value, the Promise will be resolved with the returned value. When the async function throws an exception or some value, the Promise will be rejected with the thrown value.
An async function can contain an await expression, that pauses the execution of the async function and waits for the passed Promise's resolution, and then resumes the async function's execution and returns the resolved value.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function
Usually it is used to handle data that comes from server, because when you have multiple queries it can override previous one and you will handle the wrong one.
Async/await lets you handle exactly data you are awaiting for.
This is my Javascript coding. I think the results are 1 2 3 4 5.
async function fn1 () {
console.log(1);
await fn2();
console.log(3);
};
async function fn2 () {
console.log(2);
};
fn1();
new Promise((resolve, reject) => {
console.log(4)
resolve()
}).then(() => {
console.log(5)
})
But. The results are: 1 2 4 5 3.
// functions declaration
async function fn1 () {
console.log(1);
await fn2(); // wait for async function fn2(which gets dispatched on thread 3) => so console.log(2) should be here chronologically
console.log(3);
};
async function fn2 () {
console.log(2);
};
// thread 1 starts (the actual execution code)
fn1(); // since fn1 is async it gets dispatched on thread 2
new Promise((resolve, reject) => {
console.log(4)
resolve() // trigger () => {console.log(5)}
}) // promise declaration
.then(() => {
console.log(5)
}) // execute the promise right away
Put it simple, console.log(1), console.log(2), console.log(3) happens on one thread chronologically, console.log(4), console.log(5) happens on the other thread chronologically. And they are intersecting each other.
Side Note: JavaScript itself is single-threaded and does not support multi-threading!(aside from web worker etc., in our context of "async") I simplified a lot and used the term thread here just to make it easy to be understood. In order not to mislead you on the concept of asynchronous operations, I recommend you to have a read on this QnA if you are not really sure on how async works in JavaScript.
The thing is that JS code are essentially async. So, before it executes the '3' printing, it already has fired the other instructions and theses ends before that ending.
The executions of fn1() and new Promise() above are executed asynchronously, that's why the order of instructions are independent. If you want to have your desired result you can try below code:
async function fn1 () {
console.log(1);
await fn2();
console.log(3);
};
async function fn2 () {
console.log(2);
};
async function makeExectionOrder(){ // move the blocks into asynch function
await fn1(); // make synchrounous call
new Promise((resolve, reject) => {
console.log(4)
resolve()
}).then(() => {
console.log(5)
})
}
makeExectionOrder()
You have to understand stack and queue:
More information: stack and queue video or ducumentation
When you do a new Promise(fnResolvingOrRejecting) the function fnResolvingOrRejecting is immediately called so it's on the same stack. Nothing is queued yet.
If you understand what stack and queue is you could explain your code better omitting the confusing async and await syntax.
The syntax would really improve code once you understand what it actually does but in this case I'll leave it out so you can see what code is executed as a callback/result handler and what code is on stack.
function fn1 () {
console.log(1);//on the same stack as calling fn1
fn2()//executing fn2
.then(//first queued
() => console.log(3)
);
};
function fn2 () {
return new Promise(
(resolve)=>{
console.log(2);//on the same stack as calling fn1
resolve();
}
);
};
fn1();//calling fn1 immediatly logs 1 and 2 because they are on the same stack
new Promise((resolve, reject) => {
//after calling fn1 and queing console.log(3) this is executed because it's on
// the same stack
console.log(4)
resolve()
}).then(() => {//second queued
console.log(5)
})