學習筆記:javascript中的Generator函式

請叫我宋某某發表於2019-06-21

最近在學習redux-saga,由於redux-saga需要使用Generator函式,所以下來就回顧了一下Generator

Generator 函式是 ES6 提供的一種非同步程式設計解決方案,語法行為與傳統函式完全不同

寫法上Generator函式與普通函式沒什麼區別,只是在function關鍵字後面多了一個*號,函式內部多了一個yield關鍵字

function* demo() {
  console.log(1)
  yield 111
  console.log(2)
  yield 222
  console.log(3)
  return 333
}

上面的函式即為一個Generator函式,Generator呼叫後並不會直接執行,而是返回一個指向內部狀態的指標物件需要呼叫該指標物件的next方法才會向下執行,當遇到yield關鍵字的時候,函式會掛起,交給外部,直到遇到下一次呼叫next方法

let g = demo()

此時若我們第一次呼叫

let val1 = g.next()

// 列印 1,val1 = {value: 111, done: false}

let val2 = g.next()
// 列印 2,val2 = {value: 222, done: false}

let val3 = g.next()
// 列印 3,val3 = {value: 333, done: true}

根據結果我們可以很清楚的看到當返回值的donetrue的時候就代表Generator執行完畢,此外Generator函式還可以被迴圈


for (let val of demo()) {
  console.log(val)
}
// val依次會列印出 111、222,注意當done為true的時候不會列印value的值

當然Generator在工作中最常用到的就是處理非同步函式,下面舉個例子:


function demo(a, b) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve(a + b)
    }, 1000)
  })
}


function* gen(val) {
  let a = yield demo(1, 2)
  console.log(a)
  let b = yield '123'
  return b
}

這裡需要注意的是,變數a的值是需要next方法傳入的

let g = gen()
let {value} = g.next()

此時函式中的變數a的值就是再次呼叫next傳入的值, 比如g.next(value),則此時a的值就是一個Promise的例項,顯然這不是我們想要的,我們期望拿到的是Promise.resolve的值

那麼根據上面的結果我們可以編寫一個Generator執行器

function execGen(gen) {
  let g = gen()

  function deep(val) {
    let {value, done} = g.next(val)
    if (done) {
      return
    }
    if (value instanceof Promise) {
      value.then(data => {
        deep(data)
      })
    } else {
      deep(value)
    }
  }
  deep()
}

execGen(gen)

// gen函式的console會正確的列印出值3

其實如果不是redux-saga的話我感覺我工作中基本不會用到Generator函式,個人感覺更優雅的是async/await 來處理非同步情況

async function handler() {
  try{
    let val = await demo()
  }catch (e) {
    
  }
}

最後還是期望有大佬能夠幫忙指出相比於async/await,Generator有什麼優勢^_^

相關文章