JavaScript Asynchronous Process Control Past and Present
At the beginning of the design, javascript was designed as a single-threaded language to avoid resource management complications (which threads operate dom simultaneously).
When it comes to asynchronization, callbacks have to be mentioned, and javascript has been improving this solution to solve the problem of multiple callback nesting that makes code difficult to maintain.
javascript asynchronous process control has gone through for more than 10 years
callback -> event -> promise -> yield & co -> async await
Callback
The most common method of asynchronous programming prior to ES6 is that code becomes difficult to maintain if callback functions have more nested layers. Exceptions in callback functions cannot be caught outside of callback functions.
var fs = require('fs')
try {
fs.readFile('file', 'utf8', function(err, data){
// if (err) {
// console.log(err)
// } else {
console.log(data)
// }
})
} catch(e) {
console.log(e)
}
When trying to read a file that does not exist. The outer try/catch cannot catch the exception. The output undefine.
Callback asynchronous operation, the body of the asynchronous call and callback belong to different event loops. While try/catch can only catch exceptions to the current event. Therefore, it will not be able to capture
Event (Publish/Subscribe Mode)
Use event-driven mode. Task execution is determined by an event, not by the order of the code
var EventEmitter = require('events')
var fs = require('fs')
var eve = new EventEmitter()
// Listen for read events
eve.on('read', function(value){
console.log(value)
})
// Perform an asynchronous read
fs.readFile('./template.txt', 'utf8', function (err, data) {
if (err) {
console.log(err)
} else {
// Send a read event when you get the data. Pass the data
eve.emit('read', data)
}
})
Promise
A promise object is provided in ES6. A unified API is provided to the outside world. Asynchronous operations can be expressed by the flow of synchronous operations. The disadvantage of too many nested layers in callback is avoided.
var fs = require('fs')
function read (path) {
return new Promise(function(resolve, reject){
fs.readFile(path, 'utf8', function(err, data){
if (err) {
reject(err)
} else {
console.log(data)
resolve()
}
})
})
}
read('./1.txt').then(function(){
return read('./2.txt')
}).then(function(){
return read('./3.txt')
}).then(function(){
console.log('end of execution')
})
yield && co
Encapsulate asynchronous tasks with the Generator function. Use yield where you need to pause
var fs = require('fs')
function *getData () {
var a = yield readFile('./1.txt')
console.log(a)
var b = yield readFile('./2.txt')
console.log(b)
var c = yield readFile('./3.txt')
console.log(c)
}
function readFile(path) {
return new Promise(function(resolve, reject){
fs.readFile(path, 'utf8', function (err, data) {
if (err) {
reject(err)
} else {
resolve(data)
}
})
})
}
function co(gen) {
var fn = gen()
var lastVal
return new Promise(function(resolve, reject){
!function next(lastVal) {
var {value, done} = fn.next(lastVal)
if (done) {
resolve()
} else {
value.then(next, reject)
}
}()
})
}
co(getData)
async/await
Async/Await should be the simplest asynchronous solution available
- Async means this is an async function, await can only be used inside this function
- await means wait here for promise to return the result before proceeding.
- await should be followed by a promise object (other return values are okay, just execute immediately)
- Capture error
var fs = require('fs')
async function getData () {
try {
var a = await readFile('file')
} catch(e) {
console.log(e)
}
var b = await readFile('./2.txt')
console.log(b)
var c = await readFile('./3.txt')
console.log(c)
}
function readFile(path) {
return new Promise(function(resolve, reject){
fs.readFile(path, 'utf8', function (err, data) {
if (err) {
reject(err)
} else {
resolve(data)
}
})
})
}
getData()
Reference Article
The Meaning and Usage of Generator Function