在最初学习 MFC 编程时,回调函数即是遇到的第一个难点。看着书中的定义 —— “ callback 函数虽然由你来设计,可是永远不会也不应被你调用,它们是为 Windows 系统准备的。” 我一脸的蒙圈。可是经过长时间的磨(wu)炼(jie),我终于在记忆中深深的烙上了不可缓解的不适,可谁曾想到这种不适延续到了 JS 。编程
回想 MFC 的基本思想,即 以消息为基础,以事件驱动之
。Windows 将事件转化为消息传递给 消息循环
,再由 消息循环
分发给 窗口函数
来处理。 窗口函数
即是回调函数,由于你不知道用户什么时候会产生事件,而 Windows 会清楚。 如一个 Button 的点击处理逻辑由你来设计,而调用则交给了 Windows 。 来看张图,我知道它如此熟悉,但毫不是 NodeJS:app
回到 JS 细数回调函数的几种运用:异步
const arr = [10, 20, 30, 40, 50, 60]
console.log("before")
let callback = arg => { // 回调逻辑
console.log(arg)
}
arr.forEach(callback) // forEach 调用回调逻辑
console.log("after")
复制代码
console.log("before")
let callback = () => { // 回调逻辑
console.log('hello from callback');
}
setTimeout(callback, 1000) // setTimeout 调用回调逻辑
console.log("after")
复制代码
const readable = getReadableStreamSomehow() // 业务逻辑如 fs.createReadStream('foo.txt')
function nextDataCallback (chunk) { // 回调逻辑
console.log(`Received ${chunk.length} bytes of data`)
}
function errorCallback (err) {
console.error(`Bad stuff happened: ${err}.`)
}
function doneCallback () {
console.log(`There will be no more data.`)
}
readable.on('data', nextDataCallback) // readable 调用回调逻辑
readable.on('error', errorCallback)
readable.on('end', doneCallback)
复制代码
相信你们不陌生 NodeJS 的 Event Loop 机制,它和 MFC 的处理很是类似:异步编程
由于在 NodeJS 中 Event Loop
不一样于 MFC 中的 消息循环
无须本身编写,因此须要在回调函数和 Event Loop
中创建联系,这就是添加了订阅过程的缘由。函数
经过上述几种运用能够看出,虽然回调函数在 JS 中再也不由 Windows 调用,但它依然遵循了 “你定义,但不是你调用” 的原则。同时在处理异步时,所被人诟病的回调地狱也促使新的处理方式的诞生。oop
Promise 构建在回调函数的基础上,实现了先执行异步再传递回调的方式。而这里只想简单说明回调函数和 Promise 的联系,不去纠结实现的细节,如需深刻请参考其它文献。学习
在异步应用中,Promise 将分支假设为只有两种结果:成功或失败,咱们先为此设计两个回调函数来响应结果:ui
function fulfilledCallback (data) {
console.log(data)
}
function errorCallback (error) {
console.log(error)
}
复制代码
添加业务逻辑:this
const executor = (resolve, reject) => {
setTimeout(function(){
resolve('success')
},1000)
}
复制代码
尝试执行:spa
executor(fulfilledCallback, errorCallback)
// wait one second
// 'success'
复制代码
如今咱们将业务执行封装到函数中:
function someFun(executor) {
executor(fulfilledCallback, errorCallback)
}
someFun(executor)
// wait one second
// 'success'
复制代码
在函数内添加状态表示业务执行结果:
function someFun(executor) {
this.status = 'pending' // 初始状态
this.value = undefined // 成功执行的数据
this.reason = undefined // 失败执行的缘由
executor(fulfilledCallback, errorCallback)
}
复制代码
创建状态和回调函数的联动:
function someFun(executor) {
this.status = 'pending' // 初始状态
this.value = undefined // 成功执行的数据
this.reason = undefined // 失败执行的缘由
function resolve(value) {
self.status = 'resolved'
self.value = value
fulfilledCallback(self.value)
}
function reject(reason) {
self.status = 'rejected'
self.reason = reason
errorCallback(self.reason)
}
executor(resolve, reject)
}
复制代码
添加方法 then
实现回调函数通常化,同时给函数改个名:
function myPromise(executor) {
let self = this
self.status = 'pending'
self.value = undefined
self.reason = undefined
self.fulfilledCallbacks = []
self.errorCallbacks = []
self.then = function(fulfilledCallback, errorCallback) {
self.fulfilledCallbacks.push(fulfilledCallback)
self.errorCallbacks.push(errorCallback)
}
function resolve(value) {
self.status = 'resolved'
self.value = value
self.fulfilledCallbacks.forEach(fn => {
fn(self.value)
})
}
function reject(reason) {
self.status = 'rejected'
self.reason = reason
self.errorCallbacks.forEach(fn => {
fn(self.reason)
})
}
executor(resolve, reject)
}
复制代码
再次尝试执行:
function otherFulfilledCallback (data) {
console.log('other' + ' ' + data)
}
function otherErrorCallback (error) {
console.log('other' + ' ' + error)
}
const p = new myPromise(executor)
p.then(otherFulfilledCallback, otherErrorCallback)
// wait one second
// 'other success'
复制代码
再次重申,这里只想研究回调函数如何封装进 Promise 的原理。
如今咱们将 NodeJS 的回调函数进行改进,首先放弃显式的订阅过程,将回调函数传递给业务调用:
function nextCallback(data) {
// ...
}
function errorCallback(data) {
// ...
}
function completeCallback(data) {
// ...
}
giveMeSomeData(
nextCallback,
errorCallback,
completeCallback
)
复制代码
补全回调回调函数,添加一个能够执行的业务:
function nextCallback(data) {
console.log(data)
}
function errorCallback(err) {
console.error(err)
}
function completeCallback(data) {
console.log('done')
}
function giveMeSomeData(nextCB, errorCB, completeCB) {
[10, 20, 30].forEach(nextCB)
}
giveMeSomeData(
nextCallback,
errorCallback,
completeCallback
)
复制代码
将回调函数封装进对象中,同时将业务名称改成 subscribe
,也封装进 observable
对象:
const observable = {
subscribe: function subscribe(ob) {
[10, 20, 30].forEach(ob.next)
ob.complete()
}
}
const observer = {
next: function nextCallback(data) {
console.log(data)
},
error: function errorCallback(err) {
console.error(err)
},
complete: function completeCallback(data) {
console.log('done')
}
}
observable.subscribe(observer)
复制代码
执行代码看看结果:
10
20
30
done
复制代码
再再次重申,这里只想研究回调函数如何封装进 Observable 的原理。
Generator 函数是一种强大的异步编程解决方案,在某种程度它抛弃了回调函数的理念,经过 协程
为每一个任务保持了调用栈。能够简单的认为,Generator 函数当执行到 yield
语句时,会从当前的执行上下文脱离,并在执行 next
语句返回。
本文简单介绍了回调函数及创建在其上的几种异步编程解决方案,但愿能对习惯线性思考的朋友有所帮助。