緩解異步編程的不適

回調函數

溯源

在最初學習 MFC 編程時,回調函數即是遇到的第一個難點。看着書中的定義 —— 「 callback 函數雖然由你來設計,可是永遠不會也不應被你調用,它們是爲 Windows 系統準備的。」 我一臉的蒙圈。可是經過長時間的磨(wu)煉(jie),我終於在記憶中深深的烙上了不可緩解的不適,可誰曾想到這種不適延續到了 JS 。編程

回想 MFC 的基本思想,即 以消息爲基礎,以事件驅動之。Windows 將事件轉化爲消息傳遞給 消息循環 ,再由 消息循環 分發給 窗口函數 來處理。 窗口函數 即是回調函數,由於你不知道用戶什麼時候會產生事件,而 Windows 會清楚。 如一個 Button 的點擊處理邏輯由你來設計,而調用則交給了 Windows 。 來看張圖,我知道它如此熟悉,但毫不是 NodeJS:app

MFC

理解

回到 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")
複製代碼
  • NodeJS 中,添加了訂閱過程:
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

由於在 NodeJS 中 Event Loop 不一樣於 MFC 中的 消息循環 無須本身編寫,因此須要在回調函數和 Event Loop 中創建聯繫,這就是添加了訂閱過程的緣由。函數

經過上述幾種運用能夠看出,雖然回調函數在 JS 中再也不由 Windows 調用,但它依然遵循了 「你定義,但不是你調用」 的原則。同時在處理異步時,所被人詬病的回調地獄也促使新的處理方式的誕生。oop

Promise

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 的原理。

RxJS

如今咱們將 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 函數是一種強大的異步編程解決方案,在某種程度它拋棄了回調函數的理念,經過 協程 爲每一個任務保持了調用棧。能夠簡單的認爲,Generator 函數當執行到 yield 語句時,會從當前的執行上下文脫離,並在執行 next 語句返回。

總結

本文簡單介紹了回調函數及創建在其上的幾種異步編程解決方案,但願能對習慣線性思考的朋友有所幫助。

相關文章
相關標籤/搜索