Start with an interview question
Draw lessons from the 10th problem in Yang Da Da’s arrangement
async function async1() {
console.log('async1 start');
await async2();
console.log('async1 end');
}
async function async2() {
console.log('async2');
}
console.log('script start');
setTimeout(function() {
console.log('setTimeout1');
Promise.resolve().then(function() {
console.log('Timer promise1')
})
}, 0)
setTimeout(function() {
console.log('setTimeout2');
Promise.resolve().then(function() {
console.log('Timer promise2')
})
}, 0)
async1();
new Promise(function(resolve) {
console.log('promise1');
resolve();
}).then(function() {
console.log('promise2');
});
console.log('script end');
Copy the code
Execution result first press not table, first to explore js asynchrony
Asynchronous implementation
Js is single-threaded, but the host environment is multi-threaded.
But for asynchrony to work properly, two things must be done at the right time and in the right environment
- Task queue
When an asynchronous task is encountered, the task callback is registered in the task queue, and the executable callback in the task queue is processed whenever the main thread is idle. This almost guarantees correct execution timing (waiting for the main thread to be idle, not for timer, etc.). Following this principle, Node is slightly different from the browser
But in Node 11+, Node actively smoothes the difference
- The scope chain
When a function is created, it has the [[scope]] property, which records the current scope chain. When the function completes execution, the execution environment is destroyed. But if the inner function is still referenced, the scope chain still keeps the reference to the parent function’s active object, which ensures the correct execution environment.
Next, let’s analyze the example at the beginning of this article
Enter the topic below, explore js asynchronous development process
Event/callback
From the asynchronous implementation of JS, the most direct asynchronous method – callback, take Node as an example, there is a classic error-first callback mode, node native module asynchronous APIU is almost this mode
fs.readFile('/test.txt',function(err,data){
if (err) {
thro
} else {
console.log(data)
}
})
Copy the code
The problem: Bad code habits make for callback hell
fs.readFile('/test.txt',function(err,data){
if (err) {
throw err
} else {
fs.readFile('/test1.txt',function(err,data){
if (err) {
throw err
} else {
fs.readFile('/test2.txt',function(err,data){
if (err) {
throw err
} else {
console.log(data)
}
})
}
})
}
})
Copy the code
Publish/subscribe
A common development pattern is to have interdependent modules rely on a message-centric abstraction by addressing communication between modules.
Common package: PubSub
Simple implementation:
function PubSub() { this.handles = { // eventName: [] // cbList } } PubSub.prototype.subscribe = function (eventName, callback) { if (this.handles.hasOwnProperty(eventName)) { this.handles[eventName].push(callback) } else { this.handles[eventName] = [callback] } } PubSub.prototype.notify = function (eventName, ... rest) { if (this.handles[eventName]) { this.handles[eventName].map(cb => cb.apply(this, rest)) } return this }Copy the code
Use:
const readFilePub = new PubSub()
function readFile(path) {
fs.readFile(path, (err,data) => {
if (err) {
throw err
} else {
readFilePub.notify(path, data)
}
})
}
readFile('/test1.txt')
readFilePub.subscribe('/test1.txt', () => readFile('/test2.txt'))
readFilePub.subscribe('/test2.txt', () => readFile('/test3.txt'))
readFilePub.subscribe('/test3.txt', () => {})
Copy the code
Problem: Not good for chain asynchronous operation
promise
Many JS beginners think of Promise as a solution to asynchronous callback hell, and I thought the same way when I first encountered Promise. Before Promise, no matter how you organized your code, it had to pass callbacks, and callbacks had to have a serious problem — inversion of control.
A.readFile('/test2.txt',function(err,data){
if (err) {
throw err
} else {
console.log(data)
}
})
Copy the code
For example, if this readFile is a third-party API, it may have multi-tune, under-tune, early tune, late tune… Wait, because the rights to call have been handed over to a third party.
Promises/A+ : Promises/ Promises/ Promises
- A promise must be in one of three states: Pending, depressing, and Rejected. State changes can only be pending to depressing or pending to Rejected. State change is irreversible.
- When a promise is implemented or rejected, it must have a value that cannot be changed (does not imply deep immutability, i.e. properties of reference types remain mutable)
- The THEN method of a promise takes two optional parameters that represent a callback when the promise state changes. The then method returns a promise.
- Promises/A+ ‘s THEN methods interoperate with other promised normative THEN methods that have been implemented. It also allows Promises/A+ implementations to “assimilate” unqualified implementations with reasonable THEN methods
Common package: Promise
Simple implementation:
Const PENDING = 'PENDING' // const REJECTED = 'REJECTED' // const PENDING = 'PENDING' // const REJECTED = 'REJECTED' // Promise(executor) { const self = this self.status = PENDING self.onFulfilled = [] self.onRejected = [] self.value = null function resolve(value) { if (self.status === PENDING) { self.status = FULFILLED self.value = value self.onFulfilled.map(cb => cb()) } } function reject(value) { if (self.status === PENDING) { self.status = REJECTED self.value = value self.onRejected.map(cb => cb()) } } try { executor(resolve, reject) } catch (e) { reject(e) } } Promise.prototype.then = function (onFulfilled, onRejected) { const self = this const promise2 = new Promise((resolve, This will be a big pity if (self.status === big pity) {setTimeout(() => {try {const x === big pity) onFulfilled(self.value) resolvePromise(promise2, x, resolve, reject) } catch (e) { reject(e) } }) } else if (self.status === REJECTED) { setTimeout(() => { try { const x = onRejected(self.value) resolvePromise(promise2, x, resolve, reject) } catch (e) { reject(e) } }) } else if (self.status === PENDING) { self.onFulfilled.push(() => { setTimeout(() => { try { const x = onFulfilled(self.value) resolvePromise(promise2, x, resolve, reject) } catch (e) { reject(e) } }) }) self.onRejected.push(() => { setTimeout(() => { try { let x = onRejected(self.value) resolvePromise(promise2, x, resolve, reject) } catch (e) { reject(e) } }) }) } }) return promise2 } function resolvePromise(promise2, x, resolve, Reject) {if (promise2 = = = x) {/ / avoid loop reject (new TypeError (' Chaining cycle '))} the if (x && typeof x = = = 'object' | | Typeof x === 'function') {let used () {try {const then = x.hen // thenable is the duck typeof promise, If (typeof THEN === 'function') {then.call(x, (y) => { if (used) return used = true resolvePromise(promise2, y, resolve, reject) }, (err) => { if (used) return used = true reject(err) }) }else{ if (used) return used = true resolve(x) } } catch (err) { if (used) return used = true reject(err) } } else { resolve(x) } }Copy the code
Of course, ES6 also added promise. all, Promise.race and other apis for practical business development, which is not the scope of this discussion
Use:
function readFile(path) {
return new Promise((resolve, reject) => {
fs.readFile(path, (err,data) => {
if (err) {
reject(err)
} else {
resolve(data)
}
})
})
}
readFile('/test1.txt')
.then(() => readFile('/test2.txt'))
.then(() => 1, () => 2)
.then(() => readFile('/test3.txt'))
Copy the code
Promise has the following advantages over the previous asynchronous organization:
- Compatible with various Promise class libraries to implement chain calls for easy organization of synchronous or asynchronous code
- Avoid reverse of control problems with third-party library asynchronous apis
async/await
The ultimate dream is to express asynchronous logic in synchronous code.
Before looking at async/await, let’s review the following generators
function *gen (a) { let b = yield a let c = yield b return c } const it = gen(1) console.log(it) let r1 = it.next() Console. log(r1) r1 = it. Next (2) console.log(R1) R1 = it. Next (3) console.log(r1) Object [Generator] {} {value: 1, done: false } { value: 2, done: false } { value: 3, done: true }Copy the code
From the promise results alone, we can know some of the generator’s intentions, and explore further along the lines of asynchronous applications below
Since the generator can generate multiple inputs and outputs, we place a promise in the output, meaning that the value returned by the iterator executing next is a promise
Again, the example above
function readFile(path) {
return new Promise((resolve, reject) => {
fs.readFile(path, (err,data) => {
if (err) {
reject(err)
} else {
resolve(data)
}
})
})
}
function *readAllFile() {
const data1 = yield readFile('/test1.txt')
const data2 = yield readFile('/test2.txt')
const data3 = yield readFile('/test3.txt')
return 'done'
}
Copy the code
But we’re calling the iterator for readAllFile, and we need to manually determine the promise value, and then execute next, which is gilding the lily.
Here we’ll implement a runner that runs the generator, wire up the promise, and we’ll basically have what we need.
function run(gen, ... rest) { const it = gen(... rest) function handleNext(value) { const next = it.next(value) console.log(next) return handleResult(next) } function handleResult(next) { if (next.done) { return next.value } else { return Promise.resolve(next.value).then(handleNext) } } return Promise.resolve().then(handleNext) }Copy the code
This allows us to run the generator’s asynchronous code directly and get the final result
This generator +promise+ runner works beautifully to fulfill our ultimate dream – synchronous code expressing asynchronous logic. Es7 has adopted this mode of operation as a standard, known as async functions
async function readAllFile() {
const data1 = await readFile('/test1.txt')
const data2 = await readFile('/test2.txt')
const data3 = await readFile('/test3.txt')
return 'done'
}
Copy the code
At this point, the development process of JS asynchrony is concluded, and async is basically the final form as far as it is concerned.