在最初學習 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
語句返回。
本文簡單介紹了回調函數及創建在其上的幾種異步編程解決方案,但願能對習慣線性思考的朋友有所幫助。