理解異步編程

異步的高性能爲 Node 帶來了高度的讚譽,而異步編程也爲其帶來了部分的詆譭。git

異步編程從早期的 callback、事件發佈\訂閱模式到 ES6 的 Promise、Generator 在到 ES2017 中 async,看似風格迥異,可是仍是有一條暗線將它們串聯在一塊兒的,就是但願將異步編程的代碼表達儘可能地貼合天然語言的線性思惟。github

以這條暗線將上述幾種解決方案連在一塊兒,就能夠更好地理解異步編程的原理、魅力。編程

├── 事件發佈\訂閱模式 <= Callback設計模式

├── Promise <= 事件發佈\訂閱模式promise

├── Async、Await <= Promise、Generatorapp

事件發佈\訂閱模式 <= Callback

這個模式本質上就是回調函數的事件化。它自己並沒有同步、異步調用的問題,咱們只是使用它來實現事件與回調函數之間的關聯。比較典型的有 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 <= 事件發佈\訂閱模式

使用事件發佈\訂閱模式時,須要咱們事先嚴謹地設置目標,也就是上面所說的,必需要縝密地設定好有哪些事件會發生。這與咱們語言的線性思惟很違和。那麼有沒有一種方式能夠解決這個問題,社區產出了 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 <= Promise、Generator

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 函數,這種形式無需手動執行,而且具備更好的語義。

引用:

  • 深刻淺出 NodeJS
  • ES6 入門
  • PromiseJS
相關文章
相關標籤/搜索