Async Programming with JavaScript
How to Survive in Async Hell
Song YANG
Sync Programming Is Intuitive
var x = getX(); // 1
var y = getY(); // 5
var sum = x + y;
console.log(sum); // 6
$.ajaxSetup({
async: false,
timeout: 1000
})
var foo = $.get('//google.com')
var bar = $.get('//stackoverflow.com')
var qux = $.get('//bing.com')
console.log(foo)
console.log(bar)
console.log(qux)
Intuitive Data Requesting?
What's Wrong?
Reality IsWhat We Called Reality
JavaScript Is Just A Single-Threaded Event Loop
How We Handle That?
Async Programming
-
Callbacks
-
Promises
-
Event Emitter
-
Generators / yield
-
Async / await
Async Approaches
Callbacks
function B(callback) {
// Do operation that takes some time
callback('Done!');
}
function A(message) {
console.log(message);
}
// Execute `B` with `A` as a callback
B(A);
Functions are the first-class citizens of the language
They can be passed around like any other variable to other functions
Callbacks
Error-first callbacks
fs.readFile('/etc/passwd', (err, data) => {
if (err) throw err;
console.log(data);
});
Heart of Node.js itself - the core modules are using it as well as most of the modules found on NPM.
Challenges
-
It is easy to build callback hells or spaghetti code with them if not used properly
-
Callbacks can run more than once
-
Can't return values with the return statement, nor can use the throw keyword
Callback Hell
getData(function(a){
getMoreData(a, function(b){
getMoreData(b, function(c){
getMoreData(c, function(d){
getMoreData(d, function(e){
...
});
});
});
});
});
Callback Run More Than Once
function printRandomId(callback) {
setTimeout(() => {
callback(Math.random())
callback(Math.random())
}, 0)
}
printRandomId((val) => {
console.log(val)
})
// print twice
function getY () {
var innerY
setTimeout(function () {
innerY = 'Y'
}, 0)
return innerY
}
var y = getY();
console.log(y);
// undefined
Cannot Return Value
try/catch Doesn't Work
function throwUncaughtError () {
try {
setTimeout(function() {
throw new Error('Whoops!');
}, 1000);
}
catch (e) {
alert("You won't see this!");
}
}
Promises
an alternative way to manage your asynchronous code in JavaScript
Ⅰ BASICS
A promise represents the eventual result of an asynchronous operation
[ Promises/A+]
What are promises?
Ⅰ BASICS
Pending: initial state, not fulfilled or rejected.
Fulfilled: meaning that the operation completed successfully.
Rejected: meaning that the operation failed.
Settled: once a promise is settled, it is immutable and cannot be changed.
Promises States
Ⅰ BASICS
Standard Libraries
Q.js
When.js
RSVP.js
implementations https://promisesaplus.com/implementations
thenable, jqXHR Object
Ⅱ USAGES
Using Promises
Ⅱ USAGES
Creating Promises
var somePromise = new Promise(function(resolve, reject) {
// 异步处理
// 处理结束后、调用resolve 或 reject
setTimeout(function() {
resolve('Value Resolved')
}, 1000);
});
Ⅱ USAGES
Creating Promises
new Promise(function(resolve){
resolve(42);
});
// Returns a Promise object that is resolved with the given value.
Promise.resolve(42)
Promise.resolve, returns a Promise object that is resolved with the given value.
Ⅱ USAGES - Promise.prototype
then
promise.then(onFulfilled, onRejected)
resolve(success)
onFulfilled will be called after promise is fulfilled
reject(failed)
onRejected will be called after promise is rejected
Both onFulfilled 、onRejected are optional
Ⅱ USAGES - Promise.prototype
then
Ⅱ USAGES - Promise.prototype
then
somePromise().then(function () {
// I'm inside a then() function!
});
-
return another promise
-
return a synchronous value (or undefined)
-
throw a synchronous error
Every promise gives you a then() method,
or catch(), which is just sugar for then(null, ...)
Ⅱ USAGES
Real Case
getUser('userID')
.then(onFulfilled, onRejected)
getUser('userID')
.then(function(userData) {
if(userData) return 'Success!'
else throw new Error('No user data was found...')
// your app logic error, always throw with an Error...
}, function(reason){
//handle DB errors...
})
always return or throw from inside a then() function
Ⅱ USAGES
Error Handling
getUser('userID')
.then(null, onRejected)
getUser('userID')
.catch(onRejected)
Ⅱ USAGES
Chaining Promises
getUser('userID')
.then(onFulfillOne, onRejectOne)
.then(onFulfillTwo)
.then(null, onRejectTwo)
getUser('userID')
.then(updateOtherthing)
.then(deleteStuff)
.then(logResults)
.catch(handleError)
" a promise handler always return another Promise"
Therefore it can be chained to other Promises
Ⅲ Advanced
Chains' Order
function taskA() {
console.log("Task A");
}
function taskB() {
console.log("Task B");
}
function onRejected(error) {
console.log("Catch Error: A or B", error);
}
function finalTask() {
console.log("Final Task");
}
var promise = Promise.resolve();
promise
.then(taskA)
.then(taskB)
.catch(onRejected)
.then(finalTask);
Ⅲ Advanced
Execution Order
Ⅲ Advanced
Advanced Mistakes
// case 1
login().then(function () {
return getUserInfo()
})
.then(finalHandler)
.catch(errorHandler)
// case 2
login().then(function (token) {
getUserInfo(token)
})
.then(finalHandler)
.catch(errorHandler)
// case 3
login().then(getUserInfo())
.then(finalHandler)
.catch(errorHandler)
// case 4
login().then(getUserInfo)
.then(finalHandler)
.catch(errorHandler)
Ⅲ Advanced
Puzzle #0
login().then((token) => {
return getUserInfo(token)
})
.then(finalHandler)
.catch(errorHandler)
login
|-----------|
getUserInfo(token)
|-----------|
finalHandler(userInfo)
|------------------|
Ⅲ Advanced
Puzzle #1
login().then(function () {
return getUserInfo()
})
.then(finalHandler)
.catch(errorHandler)
login
|-----------|
getUserInfo(undefined)
|-----------|
errorHandler(rejectedUserInfo)
|------------------|
Ⅲ Advanced
Puzzle #2
login().then(function (token) {
getUserInfo(token)
})
.then(finalHandler)
.catch(errorHandler)
login
|-----------|
getUserInfo(token)
|-----------|
finalHandler(undefined)
|------------------|
Ⅲ Advanced
Puzzle #3
login().then(getUserInfo())
.then(finalHandler)
.catch(errorHandler)
login
|-----------|
getUserInfo(undefined)
|---------------------|
finalHandler(token)
|------------------|
Ⅲ Advanced
Puzzle #4
login().then(getUserInfo)
.then(finalHandler)
.catch(errorHandler)
login
|-----------|
getUserInfo(token)
|-----------------|
finalHandler(userInfo)
|------------------|
Tacit programming, Point Free Style
Ⅲ Advanced
Puzzle Code
Oneline demo
https://jsbin.com/luroda/edit?js,console,output
We have a problem with promises
https://pouchdb.com/2015/05/18/we-have-a-problem-with-promises.html
Ⅲ Advanced
Async Flow
Defined in ES6
Promise.all(iterable)
Promise.race(iterable)
Other Libraries
Promise.each(iterable)
Promise.map(iterable)
Promise.map(iterable)
...
Generators / yield
Generators
What Is the Generators
Generators are functions that can be paused and resumed
function* foo () {
var index = 0;
while (index < 2) {
yield index++;
}
}
var bar = foo();
console.log(bar.next()); // { value: 0, done: false }
console.log(bar.next()); // { value: 1, done: false }
console.log(bar.next()); // { value: undefined, done: true }
Co
Generator Based Lib
Co, The ultimate generator based flow-control goodness for nodejs
co(function *(){
// resolve multiple promises in parallel
var a = Promise.resolve(1);
var b = Promise.resolve(2);
var c = Promise.resolve(3);
var res = yield [a, b, c];
console.log(res);
// => [1, 2, 3]
}).catch(onerror);
Koa
Expressive Middleware
Koa, expressive middleware for node.js using generators
var koa = require('koa');
var app = koa();
// logger
app.use(function *(next){
var start = new Date;
yield next;
var ms = new Date - start;
console.log('%s %s - %s', this.method, this.url, ms);
});
// response
app.use(function *(){
this.body = 'Hello World';
});
app.listen(3000);
Async / await
Async/await
Async functions
Under the hood async functions using Promises, it will return with a Promise
async function foo () {
var message = await operationTakesTime();
console.log(message);
}
Async/await
Async functions
function promisingOperation() {
return new Promise(function(resolve, reject) {
setTimeout(function() {
if( Math.round(Math.random()) )
resolve('Success!');
else
reject('Failure!');
}, 1000);
})
}
async function foo() {
var message = await promisingOperation();
console.log(message);
}
window.foo = foo
Async/await
await vs yield
Using yield for async is kind of hack, it's meant for lazy sequences and iterators.
await separates out the concerns by allowing yield to be used for its original purpose, and await for asynchronicity.
Async/await
Koa supports await
// Koa application is now a class and requires the new operator.
const app = new Koa();
// uses async arrow functions
app.use(async (ctx, next) => {
try {
await next(); // next is now a function
} catch (err) {
ctx.body = { message: err.message };
ctx.status = err.status || 500;
}
});
app.use(async ctx => {
const user = await User.getById(ctx.session.userid); // await instead of yield
ctx.body = user; // ctx instead of this
});
Which One Do you Prefer?
FEEDBACKS https://jinshuju.net/f/7ERv8I
Async Programming with JavaScript
By Owen Yang
Async Programming with JavaScript
- 1,599