generator及其好用的語法糖async/await

本文首發CSDNjavascript

其中關於異步操做的處理就引入了Promise和生成器。衆所周知,Promise能夠在必定程度上解決臭名昭著的回調地獄問題。可是在處理多個異步操做時採用Promise鏈式調用的語法也會顯得不是那麼優雅和直觀。而生成器在Promise的基礎上更進一步,容許咱們用同步的方式來描述咱們的異步流程。java

Generator基本原理


generator函數是ES6中一個特殊函數,經過 function* 聲明,函數體內經過 yield 來指明函數的暫停點,該函數返回一個迭代器,而且函數執行到 yield語句前面暫停,以後經過調用返回的迭代器next()方法來執行yield語句。npm

function* generator() {
    yield 1;
    yield 2;
    yield 3
}
var gen = generator()
複製代碼

generator 函數就是ES6中的生成器,生成器又能夠生成迭代器,代碼執行中斷,不會一下執行完,這樣咱們就能夠用同步的方式來描述咱們的異步流程。promise

調用generator函數後,該函數並不執行,返回的也不是函數運行的結果,而是一個指向內部狀態的指針對象,咱們能夠經過調用next方法,使指針移向下一個狀態。bash

console.log(gen.next())  //{ value: 1, done: false }
console.log(gen.next())  //{ value: 2, done: false }
console.log(gen.next())  //{ value: 3, done: false }
console.log(gen.next())  //{ value: undefined, done: true }
複製代碼

Generator 能夠實例化出一個 iterator ,而且這個 yield 語句就是用來中斷代碼的執行的,也就是說,配合 next() 方法,每次只會執行一個 yield 語句。異步

關於yield插個點

yield 後面能夠是任意合法的JavaScript表達式,yield語句能夠出現的位置能夠等價於通常的賦值表達式(好比a=3)可以出現的位置。async

b = 2 + a = 3 // 不合法
b = 2 + (a = 3) // 合法

b = 2 + yield 3 // 不合法
b = 2 + (yield 3) // 合法
複製代碼

複製代碼yield關鍵字的優先級比較低,幾乎yield以後的任何表達式都會先進行計算,而後再經過yield向外界產生值。並且yield是右結合運算符,也就是說yield yield 123等價於(yield (yield 123))函數

生成器對象方法

  1. return方法。和迭代器接口的return方法同樣,用於在生成器函數執行過程當中遇到異常或者提早停止(好比在for...of循環中未完成時提早break)時自動調用,同時生成器對象變爲終止態,沒法再繼續產生值。也能夠手動調用來終止迭代器,若是在調用return方法傳入參數,則該參數會做爲最終返回對象的value屬性值。
function* generator() {
    yield 1
    try {
        yield 2
    } finally {
        yield 3
    }
}
let genl = generator()
console.log(genl.next())    //{ value: 1, done: false }
console.log(genl.next())    //{ value: 2, done: false }
console.log(genl.return(4)) //{ value: 3, done: false }
console.log(genl.next())    //{ value: 4, done: true }
let gen =generator()        
console.log(gen.next())     //{ value: 1, done: false }
console.log(gen.return(4))  //{ value: 4, done: true }
console.log(gen.next())     //{ value: undefined, done: true }
console.log(gen.next())     //{ value: undefined, done: true }
複製代碼

return 會終結整個 Generator ,換句話說:寫在 return 後面的 yield 不會執行。 它可以中斷執行代碼的特性,能夠幫助咱們來控制異步代碼的執行順序ui

  1. throw方法。調用此方法會在生成器函數當前暫停執行的位置處拋出一個錯誤。若是生成器函數中沒有對該錯誤進行捕獲,則會致使該生成器對象狀態終止,同時錯誤會從當前throw方法內部向全局傳播。在調用next方法執行生成器函數時,若是生成器函數內部拋出錯誤而沒有被捕獲,也會從next方法內部向全局傳播

分析實現


分析如下碼哥:spa

function* generator() {
    let result = yield 'hello'
    console.log(result)
}
var gen = generator()
// console.log(gen.next()) // { value: 1, done: false }
console.log(gen.next(22)) //{ value: 1, done: false }
console.log(gen.next(2)) //2 { value: undefined, done: true }
複製代碼

第一次調用next方法傳入的參數,生成器內部是沒法獲取到的,或者說沒有實際意義,由於此時生成器函數尚未開始執行,第一次調用next方法是用來啓動生成器函數的。

const fs = require("fs").promises;
// 生成器
function * read(){
    yield fs.readFile("./name.txt","utf-8")
}
// 迭代器
let it = read()
// console.log(it.next()) // { value: Promise { <pending> }, done: false }
it.next().value.then(data=>{
    console.log(data)  // name
})
複製代碼

這是簡單的generator 實例

開始變形 文件: name.txt: age.txt age.txt: 666

const fs = require("fs").promises;
function * read(){
    let concent = yield fs.readFile("./name.txt","utf-8")
    let age = yield fs.readFile(concent,"utf-8")
    return age
}
let it = read()
it.next().value.then(data=>{
    it.next(data).value.then(data=>{
        let r = it.next(data)
        console.log(r)  // { value: '666', done: true }
    })
})
複製代碼

才兩個就這麼麻煩,還好有大神TJ的co庫 下載

npm install co 
複製代碼

co 是著名大神 TJ 實現的 Generator 的二次封裝庫

const fs = require("fs").promises;
function * read(){
    let concent = yield fs.readFile("./name.txt","utf-8")
    let age = yield fs.readFile(concent,"utf-8")
    return age
}
let co = require("co")
co(read()).then(data=>{
    console.log(data)  // 666
})
複製代碼

generator 語法糖 async/await很是好用,代碼更簡潔了!

const fs = require("fs").promises;
async function read() {
    let concent = await fs.readFile("./name.txt", "utf-8")
    let age = await fs.readFile(concent, "utf-8")
    return age
}
read().then(data => {
    console.log(data) // 666
})
複製代碼

async/await 語法糖


ES7引入的async/await語法是Generator函數的語法糖,只是前者再也不須要執行器。直接執行async函數就會自動執行函數內部的邏輯。async函數執行結果會返回一個Promise對象,該Promise對象狀態的改變取決於async函數中await語句後面的Promise對象狀態以及async函數最終的返回值。接下來重點講一下async函數中的錯誤處理。

await關鍵字以後能夠是Promise對象,也能夠是原始類型值。若是是Promise對象,則將Promise對象的完成值做爲await語句的返回值,一旦其中有Promise對象轉化爲Rejected狀態,async函數返回的Promise對象也會隨之轉化爲Rejected狀態

async function aa() {await Promise.reject('error!')}
aa().then(() => console.log('resolved'), e => console.error(e)) // error!
複製代碼

若是await以後的Promise對象轉化爲Rejected,在async函數內部能夠經過try...catch捕獲到對應的錯誤。

async function as() {
  try {
    await Promise.reject('error!')
  } catch(e) {
    console.log(e)
  }
}
as().then(() => console.log('resolved'), e => console.error(e))
// error!
// resolved
複製代碼

若是async函數中沒有對轉化爲Rejected狀態的Promise進行捕獲,則在外層對調用aa函數進行捕獲並不能捕獲到錯誤,而是會把aa函數返回的Promise對象轉化爲Rejected狀態

總結


從一開始的回調函數,到社區大佬們提出後來又加入ES6的Promise,再有generator的生成迭代,到TJ大神的co 庫,JavaScript中的代碼雖然是單線程的,異步問題的解決方式愈來愈強,generator的語法糖 async和await 和promise結合是如今的主流也是及有效的方式。

相關文章
相關標籤/搜索