JavaScript非同步流程控制的前世今生

gzc發表於2019-03-04
JavaScript

javascript在設計之初.為了避免資源管理複雜問題(多個執行緒同時操作dom,以哪個為準),因此被設計成為了單執行緒語言.

說起非同步就不得不提回撥, 為了解決多重回撥巢狀導致程式碼難以維護問題.javascript一直都在完善這個解決方案.

在10多年中javascript 非同步流程控制經過了

callback -> event -> promise -> yield & co -> async await

Callback

ES6之前非同步程式設計最常用的方法,如果回撥函式巢狀層數較深,程式碼將變得難以維護.並且在回撥函式之外無法捕獲回撥函式中的異常.

var fs = require(`fs`)

try {
  fs.readFile(`file`, `utf8`, function(err, data){
    // if (err) {
    //   console.log(err)
    // } else {
      console.log(data)
    // }
  })
} catch(e) {
  console.log(e)
}  
複製程式碼

嘗試讀取一個不存在的檔案時.外層的try/catch 無法捕獲這一異常.將輸出 undefine.
callback非同步操作,非同步呼叫的本體和callback屬於不同的事件迴圈.而try/catch 只能捕獲當前事件的異常.因此將無法捕獲

Event (釋出/訂閱模式)

採用事件驅動模式.任務的執行不取決於程式碼的順序,由某個事件來決定

var EventEmitter = require(`events`) 
var fs = require(`fs`)

var eve = new EventEmitter()

// 監聽read事件
eve.on(`read`, function(value){
  console.log(value)
})
// 執行一個非同步讀取
fs.readFile(`./template.txt`, `utf8`, function (err, data) {
  if (err) {
    console.log(err)
  } else {
    // 拿到資料後 傳送一個read事件.並傳遞資料 
    eve.emit(`read`, data)
  }
})

複製程式碼

Promise

在ES6中提供了promise物件.給外界提供了統一的API.可用同步操作的流程來表達非同步操作.避免了callback 中的巢狀層數過深,不便維護的弊端.

var fs = require(`fs`)

function read (path) {
  return new Promise(function(resolve, reject){
    fs.readFile(path, `utf8`, function(err, data){
      if (err) {
        reject(err)
      } else {
        console.log(data)
        resolve()
      }
    })
  })
}

read(`./1.txt`).then(function(){
  return read(`./2.txt`)
}).then(function(){
  return read(`./3.txt`)
}).then(function(){
  console.log(`執行結束`)
})
複製程式碼

yield && co

通過Generator函式封裝非同步任務.在需要暫停的地方使用yield

var fs = require(`fs`)

function *getData () {
 var a = yield readFile(`./1.txt`)
 console.log(a)
 var b =  yield readFile(`./2.txt`)
 console.log(b)
 var c =  yield readFile(`./3.txt`)
 console.log(c)
}

function readFile(path) {
  return new Promise(function(resolve, reject){
    fs.readFile(path, `utf8`, function (err, data) {
      if (err) {
        reject(err)
      } else {
        resolve(data)
      }
    })
  })
}

function co(gen) {
  var fn = gen()
  var lastVal
  return new Promise(function(resolve, reject){
    !function next(lastVal) {
      var {value, done}  = fn.next(lastVal)
      if (done) {
        resolve()
      } else {
        value.then(next, reject)
      }
    }()
  })
}

co(getData)
複製程式碼

async/await

Async/Await應該是目前最簡單的非同步方案了

  • async 表示這是一個async函式,await只能用在這個函式裡面
  • await 表示在這裡等待promise返回結果了,再繼續執行。
  • await 後面跟著的應該是一個promise物件(其他返回值也可以,只是會立即執行)
  • 捕獲錯誤
var fs = require(`fs`)

async function getData () {
  try {
    var a = await readFile(`file`)
  } catch(e) {
    console.log(e)
  }  
  var b =  await readFile(`./2.txt`)
  console.log(b)
  var c =  await readFile(`./3.txt`)
  console.log(c)
}

function readFile(path) {
  return new Promise(function(resolve, reject){
    fs.readFile(path, `utf8`, function (err, data) {
      if (err) {
        reject(err)
      } else {
        resolve(data)
      }
    })
  })
}

getData()
複製程式碼

參考文章
Generator 函式的含義與用法

相關文章