非同步流程處理

行走的柯南發表於2019-03-01

promise

promise 可以說是很常用的非同步處理方法

比如我們使用promise封裝一個canvas截圖的方法

clip (resolve, reject) {
    // 截圖 獲取圖片url
    return new Promise((resolve, reject) => {
      html2canvas(document.getElementById('view'), {
        canvas: canvas,
        onrendered: (canvas) => {
          let getImg = canvas.toDataURL('image/png')
          resolve(getImg)
        }
      })
    })
  }
複製程式碼

實現一個簡單的promise

function Promise (executor) {
  let self = this
  self.status = 'pending'
  self.success // 成功的原因
  self.failure // 失敗的原因

  // 事件池
  self.resolvePool = []
  self.rejectPool = []

  function resolve (data) {
    if (self.status == 'rejected') return
    self.status = 'resolved'
    self.success = data
    // 執行佇列
    self.resolvePool.forEach(function (cb){
      cb(self.success)
    })
  }
  function reject (data) {
    if (self.status == 'resolved') return
    self.status = 'rejected'
    self.failure = data
    // 執行佇列
    self.rejectPool.forEach(function (cb){
      cb(self.failure)
    })
  }

  // new一個例項就會立即執行這個promise
  executor(resolve, reject)
}

// then
Promise.prototype.then = function (onFulfilled, onRejected)  {
  let self = this
  let promiseStatus = self.status
  if (promiseStatus == 'resolved') {
    onFulfilled(self.success)
  }
  if (promiseStatus == 'rejected') {
    onRejected(self.failure)
  }

  // 很有可能此時狀態為 pending
  if (promiseStatus == 'pending') {
    // 放入事件池中等待執行
    self.resolvePool.push(function () {
      onFulfilled(self.success)
    })
    self.rejectPool.push(function () {
      onRejected(self.failure)
    })
  }
}

複製程式碼

Iterator

let target = beIteraor({name: 'mxx', address: 'beijing'}) 

target.next()
target.next()

複製程式碼

Iterator 是一個迭代器物件 每次從集合中取出一項 並且跟蹤當前序列所在位置

通過使用next方法 返回一個包含value和done兩個屬性的物件

{value: 當前物件成員, done: Boolean}

複製程式碼

Iterator 簡易實現

let target = beIteraor(['mxx', 'beijing'])

let a = target.next() 
let b = target.next()
let c = target.next()

// { value: 'mxx', done: false } { value: 'beijing', done: false } { value: 'undefined', done: true }

// 返回一個具有next方法的物件
function beIteraor(ary) {
  let index = 0
  let len = ary.length
  return {
    next: function () {
      // 呼叫next方法  返回 {value, done} 並且指標移動位置
      let done =  ~~index == ~~len
      // 如果指標位置移動到末尾 則返回undefined 否則返回當前位置成員
      let value =  done ? 'undefined' : ary[index]
      index++
      return {value, done}
    }
  }
}

複製程式碼

generator

  • generator 函式生成一個迭代器
<!-- 函式 注: 這裡的read函式直接返回檔案中內容 -->
function * getCont () {
  let name = yield 'youchangjing'
  let address = yield 'beijing'
  return name + address
}

<!-- 如何執行 -->

let getC = getCont()
getC.next() // {value: 'youchangjing', done: false}
getC.next() // {value: 'beijing' , done: false}
getC.next() // {value: undefined , done: true}
複製程式碼

可以看到 generator 函式呼叫和普通函式一樣 fn() 即可 但是函式並不會執行 只有當呼叫next方法 才能執行到第一個狀態

generator 是分段執行的 yield表示暫停執行 next方法恢復函式執行

目前來說 瀏覽器對 generator 支援情況還是很不錯的

非同步流程處理

co

如果 yield 後面是一個 promise 函式 可以配合co 庫來使用


<!-- read函式  -- 封裝的一個簡易的promise -->
function read(dir) {
  return new Promise((resolve, reject) => {
    fs.readFile(dir, 'utf-8', (err, cont) => {
      if (err) reject(err)
      resolve(cont)
    })
  })
}

<!-- generator -->

function * getCont () {
  let name = yield read('name.js')
  let address = yield read('address.js')
  return name + address
}

<!-- 配合co庫 -->

co(getCont()).then(function (cont) {
  console.log(cont) 
})

複製程式碼

簡單的CO實現原理

  • co 的引數是一個迭代器

  • co 返回的是promise 返回的promise 接受 generator 函式的 value

  • co 內部可以使 generator 函式 一直執行到 done 為TRUE

function co(iterator) {
  return new Promise(function (resolve, reject) {
    function next(cont) {
      let {value, done} = iterator.next(cont)
      <!-- value 也是一個promise  -->
      if (done) {
        <!-- 如果 done 為TRUE 則將value 傳入 resolve -->
        resolve(value)
      } else {
        <!-- done 為FALSE 則執行其then 方法 用於獲取其data傳遞給 next -->
        value.then(function (data) {
          next(data) 
          <!-- next 遞迴 -->
        }, reject)
      }
    }
    next()
  })
}
複製程式碼

async await

async await 可以看做 co + generator 的語法糖

雖然co庫可以幫我們自行處理generator 但是又要使用yield 又要封裝promise 也是有點麻煩 所以轉向 ES7 中的 async await

目前 async await 在Bable, Node 7+ 中被支援

async function getCont() {
  let people = await read('./file.js')
  let who = await read('./who.js')
  return people + who
}

getCont().then((data) => console.log(data))

複製程式碼
  • async 返回 promise

await

await 顧名思義 等待。那他在等待什麼呢。 這個取決於await 後面跟著的內容

await 'mxx'
<!-- 等待非promise -->

await IamPromise()
<!-- 等待 promise -->

複製程式碼

如果 await 等待的不是一個 promise 那麼await表示式的運算結果就是 它等到的東西 (其實await會將其轉為一個立即resolve的promise物件)

如果 await 等待的是promise 那麼他會阻塞後面的程式碼 等著Promise 物件 resolve 然後得到其值作為 await表示式的運算結果

function getRank () {
  return 12345
}

async function getType () {
  let name = await read('who.js')
  <!-- read為promise -->
  console.log(name)
  let rank = await getRank()
  <!-- getRank 為普通函式  -->
  console.log(rank)
}

複製程式碼
  • 如果我們在普通函式中使用await會被阻塞嗎

非同步流程處理

好吧~~ 直接報錯了

非同步流程處理

錯誤捕獲

async 中有兩種錯誤處理方式

1 可以在 async 函式中 使用try catch

async 中對try catch方法做了處理 使其可以捕獲非同步的錯誤


async function () {
  try {

  } catch (e) {
    console.log(e)
  }
}

複製程式碼

2 在then中進行錯誤捕獲

getCont().then().catch((e) => console.log(e))

複製程式碼

如果async函式中使用了try catch 那麼後面的then方法將會進入成功態 【相當於promise返回的是 undefined 】

相關資料

相關文章