理解异步编程

异步的高性能为 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
相关文章
相关标签/搜索