本文梳理了JavaScript异步编程从Callback、事件发布订阅模式,到Promise,再到Async/Await的演进脉络。通过源码实现解析,揭示各阶段方案如何逐步贴合线性思维,帮助开发者深入理解异步原理与最佳实践。
前言
异步编程的演进脉络
JavaScript异步编程经历了从早期的Callback、事件发布与订阅模式,到ES6引入Promise、Generator,再到ES2017标准中Async/Await的演变。尽管这些方案在语法风格上差异显著,但背后有一条贯穿始终的暗线:致力于让异步代码的表达方式更贴近人类自然的线性思维逻辑。
沿着这条将上述解决方案串联起来的暗线,我们可以更清晰地理解异步编程的核心原理及其设计魅力。演进路径大致如下:事件发布与订阅模式(源于Callback)演化为Promise,而Promise与Generator则进一步演化为Async与Await。
事件发布与订阅模式
事件发布与订阅模式与Callback的关系
该模式的本质是将回调函数事件化。它本身并不涉及同步或异步调用的问题,而是利用它来建立事件与回调函数之间的关联。Node.js中的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来直观展示其原理。
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与事件发布与订阅模式的关系
在使用事件发布与订阅模式时,需要预先严谨地定义目标,即必须细致设定可能发生的事件。这与我们的线性思维习惯相悖。社区为此提出了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)
}
}
我们使用then方法将预先定义的事件监听器存储起来,同时在executor中设定这些事件触发的时机。
从事件发布与订阅模式到Promise,带来了语义上的巨大变革。然而,仍需使用new Promise来描述整个状态转换过程,是否存在更优的实现方式?
Async/Await的核心机制
Async/Await与Promise、Generator的关系
Async与Await标准在ES2017中引入,提供了一种更为简洁的异步解决方案。
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函数,这种形式无需手动执行,且具有更好的语义表达。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持本站。文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,