Antonio Valverde
- Server side engineer
- Working at CyberAgent, Inc
- Currently working with Node.js (ES2017 usage)
Slides URL:
Full URL:
https://gitpitch.com/toniov/async-await-workshop
This workshop can be followed using the Google Chrome console:
- Windows / Linux:
Ctrl
+Shift
+J
- Mac:
Cmd
+Opt
+J
The following function will be used in the examples in this workshop:
const delay = (ms) => {
return new Promise((resolve, reject) => {
if (isNaN(ms)) {
return reject(new Error('not a valid number'));
}
setTimeout(() => {
resolve(ms * 2);
}, ms);
});
};
+++
This function returns a Promise:
- Resolved after the specified milliseconds in ms
- Fulfillment value:
ms
* 2
- Fulfillment value:
- Rejected if
ms
ms is not a valid number- Rejection value: the error message
+++
- About async/await
- Async Functions
- Handling results and errors with
await
- Things you should know
- Some Pitfalls
- Quizzes Time
async/await in 7 seconds
(Credit: twitter.com/manekinekko )
async/await is an ES2017 feature available in:
- Recent versions of modern browsers (Google Chrome, Firefox, ...)
- Node.js 8.x >
- Babel
- And more
Async function declarations:
async function foo() {}
Async function expressions:
const foo = async function () {};
Async arrow functions:
const foo = async () => {};
Async method definitions:
let obj = {
async foo() {}
}
Async functions always return Promises:
async function asyncFunc () {
return 123;
}
asyncFunc()
.then(x => console.log(x)); // 123
- Is only allowed inside async functions
- Waits for its operand, a Promise, to be settled
Is only allowed inside async functions:
function nonAsyncFunc () {
await Promise.resolve();
}
This is not allowed either:
async function asyncFunc () {
function nonAsyncFunc() {
await Promise.resolve();
}
}
Both will fail with:
Uncaught SyntaxError: Unexpected identifier
Waits for its operand, a Promise, to be settled:
- Is resolved: the fulfillment value is returned
async function asyncFunc () {
const result = await delay(100);
console.log(result);
// Output: 200
}
asyncFunc();
+++
- Is rejected: the rejection value is thrown
async function asyncFunc () {
try {
await delay("buh");
} catch (err) {
console.error(err.message);
// Output: "not a valid number"
}
}
asyncFunc();
If something besides a Promise is passed to await, it just returns the value as-is:
async function asyncFunc() {
const result = await 'hola';
console.log(result);
// Output: hola
}
asyncFunc();
That means that if an array of Promises is passed, they will not be awaited:
async function asyncFunc() {
const result = await [delay(100), delay(200)];
console.log(result);
// Output: [Promise, Promise]
}
asyncFunc();
Handling multiple asynchronous results sequentially:
async function asyncFunc() {
const result1 = await delay(100);
console.log(result1); // 200
const result2 = await delay(200);
console.log(result2); // 400
}
Equivalent to:
function asyncFunc() {
return delay(100)
.then(result1 => {
console.log(result1); // 200
return delay(200);
})
.then(result2 => {
console.log(result2); // 400
});
}
+++
Handling multiple asynchronous results concurrently:
async function asyncFunc() {
const [result1, result2] = await Promise.all([
delay(100),
delay(200),
]);
console.log(result1, result2); // 200 400
}
Equivalent to:
function asyncFunc() {
return Promise.all([
delay(100),
delay(200),
])
.then([result1, result2] => {
console.log(result1, result2); // 200 400
});
}
Handling errors:
async function asyncFunc() {
try {
await delay('buh');
} catch (err) {
console.error(err);
}
}
Equivalent to:
function asyncFunc() {
return delay('buh')
.catch(err => {
console.error(err);
});
}
+++
-
A Promise
p
is created when an async function is executed. -
Execution of the body starts synchronously.
- Execution finish via return or throw (permanently)
- Execution finish finish via
await
(temporarily, it will continue later on)
-
p
is returned
+++
Execution finish via return or throw (permanently):
async function asyncFunc() {
console.log('inside asyncFunc()');
return 'abc';
}
asyncFunc()
.then(x => console.log(`Resolved: ${x}`));
console.log('main');
// Output:
// inside asyncFunc()
// main
// Resolved: abc
+++
Execution finish finish via await
(temporarily):
async function asyncFunc() {
console.log('inside asyncFunc()');
await "dummy";
console.log('after await')
return 'abc';
}
asyncFunc()
.then(x => console.log(`Resolved: ${x}`));
console.log('main');
// Output:
// inside asyncFunc()
// main
// after await
// Resolved: abc
Returned Promises are not wrapped:
async function asyncFunc() {
return Promise.resolve(123);
}
asyncFunc()
.then(x => console.log(x)) // 123
Returning a rejected Promise leads to the result of the async function being rejected:
async function asyncFunc() {
return Promise.reject(new Error('Buh'));
}
asyncFunc().catch(err => console.error(err));
// Error: Problem!
+++
That allows us to forward fulfillments and rejections of another asynchronous computation without await
:
async function asyncFunc() {
return delay(100);
}
This works as well, but the code above(the one without await
) is more efficient:
async function asyncFunc() {
return await delay(100);
}
Remember we are dealing with Promises here
Forgetting await
:
async function asyncFunc() {
const value = delay();
if (value) {
console.log(`We have a value`);
}
}
Bugs can be difficult to detect, like in the code above.
Performance problems:
async function asyncFunc() {
const value1 = await delay(100);
const value2 = await delay(200);
const value3 = await delay(300);
console.log(`Values: ${value1}, ${value2}, ${value3}`);
}
(This is more of a bad code design problem though...)
Callback-based utilities can be tricky to use:
async function getDelayResults (array) {
return array.map(ms => {
const result = await delay(ms);
return result;
});
}
The code above will produce a SyntaxError, the innermost function that surrounds an await
is not async.
+++
This code works but the result is an array of Promises:
async function getDelayResults (array) {
return array.map(async ms => {
const result = await delay(ms);
return result;
});
}
getDelayResults([100, 200, 300])
.then(x => console.log(x)); // [Promise, Promise, Promise]
+++
It can be solved using Promise.all()
:
async function getDelayResults (array) {
const pArray = array.map(async ms => {
const result = await delay(ms);
return result;
});
const result = await Promise.all(pArray);
return result;
}
getDelayResults([100, 200, 300])
.then(x => console.log(x)); // [200, 400, 600]
+++
Or using a for of
loop:
async function getDelayResults (array) {
const result = [];
for (ms of array) {
result.push(await delay(ms));
}
return result;
}
getDelayResults([100, 200, 300])
.then(x => console.log(x)); // [200, 400, 600]
This runs in series though.
+++
-
Depending on the case the way of solving it is different
-
Solutions that run concurrently might become kind of complex
-
Same problem may occur using
Array#forEach()
,Array#reduce()
,Array#filter()
, etc.
+++
Utilities that make iteration easy:
https://github.com/toniov/p-iteration
- What will be the output?
async function ping () {
await delay(300);
console.log('ping');
}
async function pong () {
await delay(100);
console.log('pong');
}
Promise.all([ping(), pong()]);
console.log('main');
+++
+++
- What will be the output?
async function ping () {
console.log('ping');
await delay(300);
}
async function pong () {
await delay(100);
console.log('pong');
}
ping();
pong();
console.log('main');
+++
+++
- What will be the output?
async function printDelayResults (array) {
array.forEach(async ms => {
const result = await delay(ms);
console.log(result);
});
}
printDelayResults([100, 300, 200])
.then(() => {
console.log('finish');
});
console.log('main');
+++
+++
- How can the performance of this function be improved?
async function asyncFunc() {
const v1 = await delay(300);
const v2 = await delay(200);
const v3 = await delay(100);
return v1 + v2 + v3;
}
+++
+++
- Something is not necessary here
async function asyncFunc() {
await delay(100);
return await delay(200);
}
+++
+++
Exploring ES2016 and ES2017
by Dr. Axel Rauschmayer
Thanks for your attention!
Questions?