深入理解 promise、generator+co、async/await 用法

YXi發表於2019-08-19

回撥函式因為涉及的內容多而雜,並且在專案中也不怎麼使用,所以在這裡就先不說了,

本章重點講解一下 Promisegenerator + coasync/await

因為裡面內容會有點多,並且還有好多程式碼示例。所以需要靜下心慢慢看,相信看完之後,你肯定會對這三種方法涉及的非同步問題的理解更上一層樓

如果想要大致瞭解一下的話,可以看看我的這篇文章《JS中的非同步解決方案》

我們們先說Promise,然後慢慢涉及到其他,循序漸進(其實這是JS處理非同步的一個發展流程)

開始吧!!!

Promise

Promise簡單的說就是一個容器,裡面儲存著某個未來才會結束的時間(通常是一個非同步操作)的結果。從語法上說,Promise就是一個物件,從它可以獲取非同步操作的訊息。Promise提供統一的API,各種非同步操作都可以用同樣的方法處理。

如何理解:

  • 沒有非同步就不需要promise
  • promise本身不是非同步,只是我們去編寫非同步程式碼的一種方式

promise中所謂的 4 3 2 1

4大術語
一定要結合非同步操作來理解
既然是非同步,這個操作需要有個等待的過程,從操作開始,到獲取結果,有一個過程的

  • 解決(fulfill)指一個 promise 成功時進行的一系列操作,如狀態的改變、回撥的執行。雖然規範中用 fulfill 來表示解決,但在後世的 promise 實現多以 resolve 來指代之
  • 拒絕(reject)指一個 promise 失敗時進行的一系列操作
  • 終值(eventual value)所謂終值,指的是 promise 被解決時傳遞給解決回撥的值,由於 promise 有一次性的特徵,因此當這個值被傳遞時,標誌著 promise 等待態的結束,故稱之終值,有時也直接簡稱為值(value)
  • 據因(reason)也就是拒絕原因,指在 promise 被拒絕時傳遞給拒絕回撥的值

3種狀態
在非同步操作中,當操作發出時,需要處於等待狀態
當操作完成時,就有相應的結果,結果有兩種:

  • 成功了
  • 失敗了

一共是3種狀態,如下:

  • 等待態(Pending (也叫進行態)
  • 執行態(Fulfilled)(也叫成功態)
  • 拒絕態(Rejected) (也叫失敗態)

圖片載入失敗

針對每一種狀態,有一些規範:

等待態(Pending)
處於等待態時,promise 需滿足以下條件:

  • 可以遷移至執行態或拒絕態

執行態(Fulfilled)
處於執行態時,promise 需滿足以下條件:

  • 不能遷移至其他任何狀態
  • 必須擁有一個不可變的終值

拒絕態(Rejected)
處於拒絕態時,promise 需滿足以下條件:

  • 不能遷移至其他任何狀態
  • 必須擁有一個不可變的據因

2種事件
針對3種狀態,只有如下兩種轉換方向:

  • pending –> fulfilled
  • pendeing –> rejected

在狀態轉換的時候,就會觸發事件:

  • 如果是pending –> fulfiied,就會觸發onFulFilled事件
  • 如果是pendeing –> rejected,就會觸發onRejected事件

在呼叫resolve方法或者reject方法的時候,就一定會觸發事件

需要註冊onFulFilled事件 和 onRejected事件
針對事件的註冊,Promise物件提供了then方法,如下:
promise.then(onFulFilled,onRejected)

針對 onFulFilled,會自動提供一個引數,作為終值(value)
針對 onRejected,會自動提供一個引數,作為據因(reason)

1個物件
promise

注:只有非同步操作的結果,可以決定當前是哪一種狀態,任何其他的操作都無法改變這個狀態

基本使用

當我們建立 promise 時,會預設的處於 Pending 狀態,並且在建立的時候,promise 中一定要有一個執行器,並且這個執行器會立即執行

// ()=>{} 叫執行器,會立即執行
let p = new Promise(()=>{ })
// 剛建立的Promise預設處理Pending狀態
console.log(p)  // Promise { <pending> }
複製程式碼

promise 的執行器中需要傳入兩個引數,分別是 resolvereject ,在內部呼叫時,就分別代表狀態由 pending=>fulfilled(成功) pending=>rejected(失敗)

並且一旦 promise 狀態發生變化之後,之後狀態就不會再變了。比如:呼叫 resolve 之後,狀態就變為 fulfilled,之後再呼叫 reject,狀態也不會變化

let p = new Promise((resolve,reject)=>{
    resolve("有錢了....")  // 現在promise就處理成功態
})
console.log(p)  // Promise { '有錢了....' }
//失敗態就不演示了
複製程式碼

切記狀態發生變化之後,之後狀態就不會再變了

// 一個promise的狀態只能從等待到成功,或從等待到失敗
let p = new Promise((resolve,reject)=>{
    resolve("有錢了...")  // 成功了....
    reject("沒錢了...")  // 失敗了....
})
p.then(()=>{
    console.log("成功了....")
},()=>{
    console.log("失敗了...")
})
//只能輸出  成功了...
複製程式碼

then方法

上面程式碼已經看到了,在使用時可以通過promise物件的內建方法then進行呼叫,then有兩個函式引數,分別表示promise物件中呼叫resolvereject時執行的函式

let p = new Promise((resolve,reject)=>{
    // resolve("有錢了...")  // 成功了....
    reject("沒錢了...")  //失敗了....
})
// 在then方法中,有兩個引數
// 第一個參數列示從等待狀到成功態,會呼叫第1個引數
// 第二個參數列示從等待狀到失敗態,會呼叫第2個引數
p.then(()=>{
    console.log("有錢了....")
},()=>{
    console.log("沒錢了...")
})
//輸出結果 沒錢了...
複製程式碼

在執行完後,成功肯定有一個成功的結果 失敗肯定有一個失敗的原因,那麼如何得到成功的結果 ? 如何得到失敗原因呢?

let p = new Promise((resolve,reject)=>{
    // 呼叫reolve時,可以把成功的結果傳遞下去
    // resolve("有錢了...")  // 成功了...
    // 呼叫reject時,可以把失敗的原因傳遞下去
    reject("沒錢了...")  // 失敗了....
})
p.then((suc)=>{
    console.log(suc)
},(err)=>{
    console.log(err)
})
//輸出結果  沒錢了...
複製程式碼

當我們在執行失敗處理時,也可以用 throw ,就是丟擲一個錯誤物件,也是失敗的

如下:

let p = new Promise((resolve,reject)=>{
    // throw 一個錯誤物件  也是失敗的
    throw new Error("沒錢了...")
})
p.then((suc)=>{
    console.log(suc)
},(err)=>{
    console.log(err)
})
複製程式碼

throw的定義:throw語句用來丟擲一個使用者自定義的異常。當前函式的執行將被停止(throw之後的語句將不會執行),並且控制將被傳遞到呼叫堆疊中的第一個catch塊。如果呼叫者函式中沒有catch塊,程式將會終止。

//嘗試一下
function getRectArea(width, height) {
  if (isNaN(width) || isNaN(height)) {
    throw "Parameter is not a number!";
  }
}
try {
  getRectArea(3, 'A');
}
catch(e) {
  console.log(e);
  // expected output: "Parameter is not a number!"
}

複製程式碼

promise本身是同步的

// promise本身是同步的
console.log("start")
let p = new Promise(()=>{
    console.log("哈哈")  // 哈哈
})
console.log("end")  
//輸出順序   start  哈哈   end
複製程式碼

並且在執行器的內部也是可以寫非同步程式碼的

那麼then中的方法什麼時候呼叫呢?
只有當呼叫resolvereject時才會去執行then中的方法**

let p = new Promise((resolve,reject)=>{
    setTimeout(()=>{
        console.log("setTimeout")
        // resolve("有錢了...")
        reject("沒錢了...")
    },1000)
})
p.then((suc)=>{
    console.log(suc)  // 有錢了...
},(err)=>{
    console.log(err)  // 沒錢了...
})
複製程式碼

鏈式呼叫(重點)

首先我們在目錄下面建兩個檔案,分別是:name.txtage.txt
name.txt檔案裡寫了一個 age.txt
age.txt檔案裡寫了一個 666

下面就以讀取檔案為例,來演示鏈式呼叫(需要了解一點node基礎)

當你讀取檔案的時候,如果你用的是 vscode 編輯器,裡面會有一個小bug,用相對路徑可能會出錯,所以最好使用絕對路徑

讀取檔案:

let fs = require("fs")
let path = require("path")
let filename = path.join(__dirname,"name.txt")
fs.readFile(filename,"utf8",(err,data)=>{
    if(err){
        console.log(err)
    }
    fs.readFile(path.join(__dirname,data),"utf8",(err,data)=>{
        if(err){
            console.log(err)
        }
        console.log(data)
    })
})
//輸出結果 666
複製程式碼

如果用這種方法,就會出現 回撥地獄 ,很難受,所以一般不用

在讀取檔案時,我們可以專門 封裝一個函式 ,功能就是讀取一個檔案的內容

let fs = require("fs")
// 封裝一個函式,函式的功能是讀取一個檔案的內容
// rest引數(下去自己瞭解一下,就是可以獲取到傳過來的所有內容)  
function readFile(...args){
    return new Promise((resolve,reject)=>{
        fs.readFile(...args,function(err,data){
            if(err) reject(err)
            resolve(data)
        })
    });
}
//讀檔案
readFile("./name.txt","utf8").then(data=>{
    console.log(data)   
},err=>{
    console.log(err)  
})
//輸出結果  age.txt
複製程式碼

如果檔案不存在,會走第二個函式

let fs = require("fs")
function readFile(...args){
    return new Promise((resolve,reject)=>{
        fs.readFile(...args,function(err,data){
            if(err) reject(err)
            resolve(data)
        })
    });
}
// 如果name1不存在,走then的第2個函式
readFile("./name1.txt","utf8").then(data=>{
    console.log(data)  
},err=>{
    console.log(err)   
})
//報錯  no such file or directory
複製程式碼

那麼如果我們想要讀取age.txt裡面的內容呢?
我們可以這麼寫:

let fs = require("fs")
function readFile(...args){
    return new Promise((resolve,reject)=>{
        fs.readFile(...args,function(err,data){
            if(err) reject(err)
            resolve(data)
        })
    });
}
 
readFile("./name.txt","utf8").then(data=>{
    // console.log(data)  // age.txt
    readFile(data,"utf8").then(data=>{
        console.log(data)  // 666
    },err=>{
        console.log(err)  
    })
},err=>{
    console.log(err)  
})
//輸出結果  666
複製程式碼

這樣寫就可以獲取到age.txt檔案裡面的內容,但是呢,這樣寫又回到了 回撥地獄 ,不是說這種方法不行,而是不夠優雅

使用 鏈式呼叫
promise中可以鏈式呼叫 就是 .then 之後,還可以 .then ,你可以無數次的 .then
.then 之後又返回了一個新的 promise,就是.then的函式引數中會預設返回promise物件,所以當你碰到.then連續呼叫的時候,你就可以把前面的所有程式碼當成一個promise

let fs = require("fs")
function readFile(...args){
    return new Promise((resolve,reject)=>{
        fs.readFile(...args,function(err,data){
            if(err) reject(err)
            resolve(data)
        })
    });
}

readFile("./name.txt","utf8").then(data=>{
    // console.log(data)  // age.txt
    return false;
},err=>{
    console.log(err)  
}).then(data=>{  // 這裡面的data是上一個then中的第一個函式的返回值,這個.then前面的一坨程式碼就可以當成一個promise
    console.log(data)  // false
},err=>{

})
複製程式碼

如果沒有這個檔案,則返回錯誤資訊

let fs = require("fs")
function readFile(...args){
    return new Promise((resolve,reject)=>{
        fs.readFile(...args,function(err,data){
            if(err) reject(err)
            resolve(data)
        })
    });
}
readFile("./name1.txt","utf8").then(data=>{
    return data;
},err=>{
    return err;
    // console.log(err)  
}).then(data=>{  
    console.log(data)
},err=>{
    console.log(err)
})
//輸出結果   no such file or directory
複製程式碼

但是如果我們返回一個promise呢?
那麼這個promise會執行,並且會採用他的狀態

let fs = require("fs")
function readFile(...args){
    return new Promise((resolve,reject)=>{
        fs.readFile(...args,function(err,data){
            if(err) reject(err)
            resolve(data)
        })
    });
}

readFile("./name.txt","utf8").then(data=>{
    return data;
},err=>{
    return err;
    // console.log(err)  
}).then(data=>{  
    // console.log(data)
    return new Promise((resolve,reject)=>{	//返回一個promise
        reject("不OK")	//下面的.then採用這個狀態(失敗態)
    })
},err=>{}).then(data=>{
    console.log(data)
},err=>{
    console.log(err)  // 不OK
})
//輸出結果  不OK
複製程式碼

所以如果返回的是一個promise,那麼這個promise會執行,並且會採用它的狀態

小總結:

如果在上一個then的第一個函式中,返回一個普通值,那麼無論你是在第1個函式中返回,還是在第2個函式中返回,都會作為下一個then的成功的結果,如果不返回,undefined就作為下一個then的成功的結果

如果返回的是一個promise,會作為下一個thenpromise物件,data err去promise物件中去取,也就是說,前一個then的返回值,會作為後一個then的引數

再給兩個小例子,自己看一下:

let p = new Promise((resolve,reject)=>{
    setTimeout(()=>{
        resolve("hello")
    },1000)
})
p.then(data=>{
    return new Promise((resolve,reject)=>{
        setTimeout(()=>{
            resolve("world")
        },1000)
    })
}).then(data=>{
    console.log(data)  
},err=>{

})
//輸出結果 world
複製程式碼
let p = new Promise((resolve,reject)=>{
    resolve("hello")
})
let p1 = p.then(data=>{
    return new Promise((resolve,reject)=>{
        setTimeout(()=>{
            reject("不OK")
        },1000)
    })
})
p1.then(data=>{
    console.log(data)  
},err=>{
    console.log(err) 
})
//輸出結果   不OK
複製程式碼

一個坑(迴圈引用)

接下來說一個小問題,鏈式呼叫中的 迴圈引用

有的人不喜歡把前面的一大堆程式碼後面加.then,所以就用了下面的一種寫法(有可能會出現 迴圈引用):

let p = new Promise((resolve,reject)=>{
    resolve("hello")
})

let p1 = p.then(data=>{	
    return p1	//迴圈引用 報錯 p1在等p1的狀態改變,也就是我在等我吃飯,顯然是不行的
})

p1.then(data=>{
    console.log(data)
},err=>{
    console.log("-----",err)	//可執行,然後報錯
})
複製程式碼

如果我們把狀態確定住,那就可以了

let p = new Promise((resolve,reject)=>{
    resolve("hello")
})
let p1 = p.then(data=>{
    // return 123  相當於把等待態改變成成功態
    return 123
})
p1.then(data=>{
    console.log(data)  // 123
},err=>{
    console.log("-----",err)
})
//輸出 123 
複製程式碼

當然改變成失敗態也可以

let p = new Promise((resolve,reject)=>{
    resolve("hello")
})
let p1 = p.then(data=>{
    //  return new Error("不OK") 把等待態變成失敗態
    return new Error("不OK")
})
p1.then(data=>{
    console.log(data) 
},err=>{
    console.log(err)  // Error: 不OK
})
複製程式碼

遞迴解析

看一個問題

let p = new Promise((resolve,reject)=>{
    resolve("hello")
})
let p1 = p.then(data=>{
    return new Promise((resolve,reject)=>{
        setTimeout(()=>{
            resolve(new Promise((resolve,reject)=>{
                setTimeout(()=>{
                    resolve("666")
                },1000)
            }))
        },1000)
    })
})
// data是promise 還是666
p1.then(data=>{
    console.log(data)  
},err=>{
    console.log(err) 
})
複製程式碼

按理說,data列印出來的是一個promise,就是上面resolve裡面的一堆程式碼
然而並不是,promise會 進行遞迴解析.到最後上面程式碼會列印出來 666

如果在resolvereject中又是一個promise,那麼就會遞迴解析(無論有多少個promise)

let p = new Promise((resolve, reject) => {
    resolve("hello")
})
let p1 = p.then(data => {
    return new Promise((resolve, reject) => {
        resolve(new Promise((resolve, reject) => {
            resolve(new Promise((resolve, reject) => {
                resolve(new Promise((resolve, reject) => {
                    resolve(new Promise((resolve, reject) => {
                        resolve(new Promise((resolve, reject) => {
                            resolve("666")
                        }))
                    }))
                }))
            }))
        }))
    })
})
p1.then(data => {
    console.log(data)
}, err => {
    console.log(err)
})
//列印結果  666
複製程式碼

catch方法

  • catch方法,用於註冊 onRejected 回撥
  • catch其實是then的簡寫,then(null,callback)

catch就是.then的語法糖

如果.then中有第2個函式,在這個.then後面又有catch,如果到失敗態,那麼會走.then的第2個函式

let p = new Promise((resolve,reject)=>{
    reject("不OK")
})

p.then(data=>{

},err=>{
    console.log("1",err)	
}).catch(err=>{
    console.log("2",err)
})
//輸出結果 1 不OK
複製程式碼

如果.then中沒有第2個函式,在這個.then後面又有catch,如果到失敗態,那麼會走catch

let p = new Promise((resolve,reject)=>{
    reject("不OK")
})
p.then(data=>{

}).catch(err=>{
    console.log("2",err)
})
//輸出結果  2 不OK
複製程式碼

一個坑

.then第二個函式中,return err 這個 err 它是 return 到了下一個.then的第一個函式中

let p = new Promise((resolve,reject)=>{
    reject("不OK")
})
p.then(data=>{

},err=>{
    // 在這裡它並沒有reutrn到err中,它reutrn到第一個引數中
    return err
}).then(data=>{
    console.log("data----",data)
},err=>{
    console.log("err----",err)
})
//輸出結果 data---- 不OK
複製程式碼

所以最終:
一個promise中,一般在then中只有一個函式,在then後面有一個catch,一般使用then來獲取data,在catch中獲取err

let p = new Promise((resolve,reject)=>{
    resolve("OK")
})

p.then(data=>{
    console.log(data)
}).catch(err=>{
    console.log(err)
})
複製程式碼

靜態方法

Pomise類上面,提供了幾個靜態方法:

  • resolve
  • reject
  • finally
  • all
  • race

resolve

Promise.resolve("有錢了...").then(data=>{
     console.log(data)  // 有錢了...
})
複製程式碼

等價於下面這種寫法:

let p = new Promise((resolve,reject)=>{
    resolve("有錢了...")
})
p.then(data=>{
    console.log(data)
})
複製程式碼

reject

Promise.reject("沒錢了...").catch(data=>{
    console.log(data)  // 沒錢了...
})
複製程式碼

finally

不管轉成成功態還是失敗態,都會呼叫finally這個方法

Promise.resolve("有錢").then(data=>{
    console.log(data)
}).finally(()=>{
    console.log("開心...")
})
//列印結果  有錢   開心...
複製程式碼
Promise.reject("沒錢").catch(data=>{
    console.log(data)
}).finally(()=>{
    console.log("不開心...")
})
//列印結果  沒錢   不開心...
複製程式碼

all

all表示[ ]中的promise都成功了,才能得到最終的值

注意裡面是一個陣列 讀取name.txtage.txt中的內容

let fs = require("fs").promises;
// all表示[]中的promise都成功了,才能得到最終的值
Promise.all([fs.readFile("./name.txt","utf-8"),fs.readFile("./age.txt","utf-8")]).then(data=>{
    console.log(data) // [ 'age.txt', '666' ]
})
//列印結果  [ 'age.txt', '666' ]
複製程式碼

如果有一個不成功,那麼就不行

let fs = require("fs").promises;
Promise.all([fs.readFile("./name1.txt","utf-8"),fs.readFile("./age.txt","utf-8")]).then(data=>{
    console.log(data)
})
//這個是不行的
複製程式碼

race

顧名思義,race就是賽跑的意思,意思就是說,Promise.race([p1, p2, p3])裡面哪個結果獲得的快,就返回那個結果,不管結果本身是成功狀態還是失敗狀態。

let fs = require("fs").promises;
Promise.race([fs.readFile("./name.txt","utf-8"),fs.readFile("./age.txt","utf-8")]).then(data=>{
    console.log(data) // age.txt
})
複製程式碼

改變檔案裡面的內容,多嘗試幾次

generator + co

我很佩服你能看到這裡,厲害

先說 生成器 和 迭代器

生成器可以生成迭代器,可以讓程式中斷,不會把 { } 中的程式碼全部執行

用法:在function和自己聲名的名稱之間加一個 * 號,裡面用yield
產出資料,然後呼叫生成器生成迭代器

function * read(){
    yield 1;  // 只有產出,並不執行
}
//呼叫生成器 生成 迭代器   it就是迭代器  
let it = read()
複製程式碼

生成器可以產出很多值,迭代器只能next一下,拿一個值,next一下,拿一個值

function * read(){
    yield 1;  
}
let it = read()
console.log(it.next())  // { value: 1, done: false }
console.log(it.next())  // { value: undefined, done: true }
複製程式碼
function * read(){
    yield 1;  
    yield 2;
    yield 3;
}
// 呼叫read()  返回值是迭代器
let it = read()
console.log(it.next())  // { value: 1, done: false }
console.log(it.next())  // { value: 2, done: false }
console.log(it.next())  // { value: 3, done: false }
console.log(it.next())  // { value: undefined, done: true }
複製程式碼

如果 next 中有引數的話,那麼他會把這個引數傳給上一個生成器宣告的變數裡

所以第一個 next 中的引數沒有任何意義,我們一般不寫

function * read(){
    let a = yield 1; 
    console.log(a)    // 9
    let b = yield 2;
    console.log(b)    // 10
    let c = yield 3;
    console.log(c)   // 11
}
let it = read()
console.log(it.next())   // { value: 1, done: false }
console.log(it.next(9))    // { value: 2, done: false }
console.log(it.next(10))   // { value: 3, done: false }
console.log(it.next(11))   // { value: undefined, done: true }
複製程式碼

接下來用這個實現我們的讀檔案操作,哈哈,是不是很噁心

讀取name.txt檔案

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=>{	//因為是一個物件,所以直接.value
    console.log(data)  
})
//輸出結果 age.txt
複製程式碼

然後讀取age.txt檔案

const fs = require("fs").promises;
function * read(){
    let concent = yield fs.readFile("./name.txt","utf-8")
    yield fs.readFile(concent,"utf-8")

}
let it = read()
it.next().value.then(data=>{
    // console.log(data)  
    // console.log(it.next(data)) // { value: Promise { <pending> }, done: false }
    it.next(data).value.then(data=>{
        console.log(data)  
    })
})
//輸出結果 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 }
    })
})
複製程式碼

是不是感覺又陷入了 回撥地獄

那麼就用 co 吧

安裝co npm i co

用上來:

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
複製程式碼

是不是簡單多了,爽不爽

co庫可以實現自動迭代
既然是自動執行,那麼promiseexecutor中先執行一次it.next()方法,返回valuedonevalue是一個pendingPromise;如果done=false,說明還沒有走完,繼續在value.then的成功回撥中執行下一次next,即呼叫read方法;直到donetrue,走完所有程式碼,呼叫resolve;中間有任何一次next異常,直接呼叫reject,停止迭代

總結:

  • function關鍵字與函式名之間有一個星號
  • 函式體內部使用yield語句,定義不同的內部狀態
  • yield會將函式分割成好多個部分,每產出一次,就暫停一次
  • Genenrator是一個生成器,呼叫Genenrator函式,不會立即執行函式體,只是建立了一個迭代器物件,如上例中的it就是呼叫read這個Generator函式得到的一個迭代器
  • 迭代器有一個next方法,呼叫一次就會繼續向下執行,直到遇到下一個yieldreturn
  • next()方法可以帶一個引數,該引數會被當做上一條yield語句的返回值,並賦值給yield前面等號前的變數
  • 每遇到一個yield,就會返回一個{value:xxx,done:bool}的物件,然後暫停,返回的value就是跟在yield後面的返回值,done表示這個generator是否已經執行結束了
  • 當遇到return時,return後的值就是value值,done此時就是true
  • 函式末尾如果沒有return,就是隱含的return undefined
  • 使用co庫,可以自動的將generator迭代
  • co執行會返回一個promise,用then註冊成功/失敗回撥
  • co將迭代器it作為引數,這裡每呼叫一次read,就執行一次next

async/await

被稱為 非同步解決 的終極方案

async、await是什麼?

async顧名思義是“非同步”的意思,async用於宣告一個函式是非同步的。

await從字面意思上是“等待”的意思,就是用於等待非同步完成。通俗來說,就是await在這裡等待promise返回結果了,再繼續執行。並且await只能在async函式中使用

通常asyncawait都是跟隨Promise一起使用的。
為什麼這麼說呢?因為 async返回的都是一個Promise物件,同時async適用於任何型別的函式上。這樣await得到的就是一個Promise物件(如果不是Promise物件的話那async返回的是什麼 就是什麼);
注: await 不僅僅用於等 Promise 物件,它可以等任意表示式的結果,所以,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
})
複製程式碼

是不是比上面的寫法還爽呢?

await命令後面的 Promise 物件,執行結果可能是 rejected,所以最好把 await 命令放在 try...catch 程式碼塊中
如下:

async function myFunction() {
  try {
    await somethingThatReturnsAPromise();
  } catch (err) {
    console.log(err);
  }
}

// 另一種寫法

async function myFunction() {
  await somethingThatReturnsAPromise().catch(function (err){
    console.log(err);
  });
}
複製程式碼

總結:

async:

  • async函式會返回一個Promise物件
  • 如果async函式中是return一個值,這個值就是Promise物件中resolve的值
  • 如果async函式中是throw一個值,這個值就是Promise物件中reject的值

await:

  • await只能在async函式中使用
  • await後面要跟一個promise物件
  • awaitpromise返回結果後,在繼續執行

這三種方法都是用來解決非同步的,很很很重要

通常到公司面試的時候,面試官都會問到:

  • 說一下非同步的發展流程
  • 說一下非同步的解決方案

那麼看完本章內容就派上大用場了哦!

好了本章就先到此結束了,我們下期再見


^_<

相關文章