異步的高性能爲 Node 帶來了高度的讚譽,而異步編程也爲其帶來了部分的詆譭。git
異步編程從早期的 callback、事件發佈\訂閱模式到 ES6 的 Promise、Generator 在到 ES2017 中 async,看似風格迥異,可是仍是有一條暗線將它們串聯在一塊兒的,就是但願將異步編程的代碼表達儘可能地貼合天然語言的線性思惟。github
以這條暗線將上述幾種解決方案連在一塊兒,就能夠更好地理解異步編程的原理、魅力。編程
├── 事件發佈\訂閱模式 <= Callback設計模式
├── Promise <= 事件發佈\訂閱模式promise
├── Async、Await <= Promise、Generatorapp
這個模式本質上就是回調函數的事件化。它自己並沒有同步、異步調用的問題,咱們只是使用它來實現事件與回調函數之間的關聯。比較典型的有 NodeJS 的 events 模塊異步
const { EventEmitter } = require('events')
const eventEmitter = new EventEmitter()
// 訂閱
eventEmitter.on("event", function(msg) {
console.log("event", msg)
})
// 發佈
eventEmitter.emit("event", "Hello world")
複製代碼
那麼這種模式是如何與 Callback 關聯的呢?咱們能夠利用 Javascript 簡單實現 EventEmitter,答案就顯而易見了。async
class usrEventEmitter {
constructor () {
this.listeners = {}
}
// 訂閱,callback 爲每一個 event 的偵聽器
on(eventName, callback) {
if (!this.listeners[eventName]) this.listeners[eventName] = []
this.listeners[eventName].push(callback)
}
// 發佈
emit(eventName, params) {
this.listeners[eventName].forEach(callback => {
callback(params)
})
}
// 註銷
off(eventName, callback) {
const rest = this.listeners[eventName].fitler(elem => elem !== callback)
this.listeners[eventName] = rest
}
// 訂閱一次
once(eventName, callback) {
const handler = function() {
callback()
this.off(eventName, handler)
}
this.on(eventName, handler)
}
}
複製代碼
上述實現忽略了不少細節,例如異常處理、多參數傳遞等。只是爲了展現事件訂閱\發佈模式。異步編程
很明顯的看出,咱們使用這種設計模式對異步編程作了邏輯上的分離,將其語義化爲函數
// 一些事件可能會被觸發
eventEmitter.on
// 當它發生的時候,要這樣處理
eventEmitter.emit
複製代碼
也就是說,咱們將最初的 Callback 變成了事件監聽器,從而優雅地解決異步編程。
使用事件發佈\訂閱模式時,須要咱們事先嚴謹地設置目標,也就是上面所說的,必需要縝密地設定好有哪些事件會發生。這與咱們語言的線性思惟很違和。那麼有沒有一種方式能夠解決這個問題,社區產出了 Promise。
const promise = new Promise(function(resolve, reject) {
try {
setTimeout(() => {
resolve('hello world')
}, 500)
} catch (error) {
reject(error)
}
})
// 語義就變爲先發生一些異步行爲,then 咱們應該這麼處理
promise.then(msg => console.log(msg)).catch(error => console.log('err', error))
複製代碼
那麼這種 Promise 與事件發佈\訂閱模式有什麼聯繫呢?咱們能夠利用 EventEmitter 來實現 Promise,這樣可能會對你有所啓發。
咱們能夠將 Promise 視爲一個 EventEmitter,它包含了 { state: 'pending' }
來描述當前的狀態,同時偵聽它的變化
{ state: 'fulfilled' }
,要作些什麼 on('resolve', callback)
;{ state: 'rejected' }
,要作些什麼 on('reject', callback)
。具體實現以下
const { EventEmitter } = require('events')
class usrPromise extends EventEmitter {
// 構造時候執行
constructor(executor) {
super()
// 發佈
const resolve = (value) => this.emit('resolve', value)
const reject = (reason) => this.emit('reject', reason)
if (executor) {
// 模擬 event loop,注此處利用 Macrotask 來模擬 Microtask
setTimeout(() => executor(resolve, reject))
}
}
then(resolveHandler, rejectHandler) {
const nextPromise = new usrPromise()
// 訂閱 resolve 事件
if (resolveHandler) {
const resolve = (data) => {
const result = resolveHandler(data)
nextPromise.emit('resolve', result)
}
this.on('resolve', resolve)
}
// 訂閱 reject 事件
if (rejectHandler) {
const reject = (data) => {
const result = rejectHandler(data)
nextPromise.emit('reject', result)
}
this.on('reject', reject)
} else {
this.on('reject', (data) => {
promise.emit('reject', data)
})
}
return nextPromise
}
catch(handler) {
this.on('reject', handler)
}
}
複製代碼
引用於 gist.github.com/dmvaldman/1… 爲了更好地模擬 Promise 機制,修改了一點。
若是須要進一步瞭解事件機制,見https://juejin.im/post/5c4041805188252420629086。
咱們使用 then
方法來將預先須要定義的事件偵聽器存放起來,同時在 executor
中設定這些事件該在何時實行。
能夠看出從事件發佈\訂閱模式到 Promise,帶來了語義上的巨大變革,可是仍是須要使用 new Promise 來描述整個狀態的轉換,那麼有沒有更好地實現方式呢?
async、await 標準是 ES 2017 引入,提供一種更加簡潔的異步解決方案。
async function say(greeting) {
return new Promise(function(resolve, then) {
setTimeout(function() {
resolve(greeting)
}, 1500)
})
}
;(async function() {
let v1 = await say('Hello')
console.log(v1)
let v2 = await say('World')
console.log(v2)
})()
複製代碼
await 能夠理解爲暫停當前 async function 的執行,等待 Promise 處理完成。。若 Promise 正常處理(fulfilled),其回調的resolve函數參數做爲 await 表達式的值。
async、await 的出現,減小了多個 then
的鏈式調用形式的代碼。下面咱們結合 Promise 與 Generator 來實現 async、await
function async(makeGenerator) {
return function() {
const generator = makeGenerator.apply(this, arguments)
function handle({ value, done }) {
if (done === true) return Promise.resolve(value)
return Promise.resolve(value).then(
(res) => {
return handle(generator.next(res))
},
function(err) {
return handle(generator.throw(err))
}
)
}
try {
return handle(generator.next())
} catch (ex) {
return Promise.reject(ex)
}
}
}
async(function*() {
var v1 = yield say('hello')
console.log(1, v1)
var v2 = yield say('world')
console.log(2, v2)
})()
複製代碼
本質上就是利用遞歸完成 function* () { ... }
的自動執行。相比與 Generator 函數,這種形式無需手動執行,而且具備更好的語義。
引用: