想寫js異步,但是我覺得這是很大一個工程。畢竟異步包含的東西太多了。慢慢歸納吧。這篇文章就是簡單介紹一下generator函數。
Generator 函數是 ES6 提供的一種異步編程解決方案。有人稱爲生成器。
- 首先可以把它理解是一個狀態機,封裝了多個內部狀態。
- 還是一個遍歷器對象生成函數。因爲執行 Generator 函數會返回一個遍歷器對象,返回的遍歷器對象,可以依次遍歷 Generator 函數內部的每一個狀態。
Generator 函數 兩個特徵
- function關鍵字與函數名之間有一個星號,星號位置沒有明確規定,下邊四個寫法都可以。
- 函數體內部使用yield表達式,定義不同的內部狀態
舉幾個例子分析一下Generator
看一下generator函數和普通函數的區別:
正常函數調用之後就正常執行啦。
但是???generator函數莫得反應。
那怎麼才能讓它運行呢?
需要用到next方法。
返回一個遍歷器對象
function *greet() {
console.log("Hello world!")
yield 'hello----------yield'
console.log("Hello world!111")
yield 'world----------yield'
console.log("Hello world!222")
return "Hello world!"
}
let x = greet()
- 1個next,輸出到Hello world!,也就是到第一個yield之前停止了
- 2個next,輸出到Hello world!111,也就是到第二個yield之前停止了
- 3個next,輸出到Hello world!222,不知道哪裏停止啦
- 4個next,沒輸出了,就證明第三個next的時候已經執行完了
這個例子可以證明,generator函數會返回一個遍歷器,遍歷器調用next方法纔會執行函數體中的代碼。但是遇到yield會停止。
前邊說到過這麼一句話:可以把它理解是一個狀態機,封裝了多個內部狀態。執行 Generator 函數會返回一個遍歷器對象。 現在解釋了“會返回一個遍歷器對象”,但是封裝內部狀態的是什麼?
封裝了多個內部狀態
封裝了多個內部狀態就是yield和return代表其狀態。
每個next運行的時候,都是遇到yield就停止運行。如果沒有yield就會直接運行到結束。有return就返回return的值,沒有就返回undefined。
因此我函數調用寫四個next是沒用的嗷,因爲第三個的時候已經運行結束了。
我上邊的代碼裏也可以看到,yield和return後邊都是有內容的,那個內容就可以當作當前狀態,我們怎麼樣才能看到這個狀態呢?
function *greet() {
yield 'hello----------yield'
yield 'world----------yield'
return "Hello world!"
}
let x = greet()
console.log(x.next())
直接打印x.next()即可:x.next()執行之後會返回一個對象,value值是yield之後的內容,done是布爾值,如果Generator 函數每執行完就是false,執行完了就是true。
那再思考一下:yield後邊只能跟字符串?接別的試試。
function *greet() {
yield 1
yield false
yield null
yield undefined
yield [1,2,3]
yield {name:'gen'}
}
let x = greet()
console.log(x.next())
下圖可以看出:
- yield後邊跟什麼數據類型都可以
- 函數運行完,沒有設置return,就返回undefined
那我接個函數試試
function *greet() {
console.log(111)
yield ()=>console.log("我是箭頭函數")
yield (()=>console.log("我是調用的箭頭函數"))()
}
let x = greet()
console.log(x.next())
- 第一個next執行到第一個yield前邊,並且返回值中的value是第一個yield後邊的內容。
- 第二個next執行到第二個yield前邊,並且返回值中的value是第二個yield後邊的內容。
- 因爲第二個yield後邊的函數調用了,並且沒返回值,所以value是undefined
- 第三個next執行完整個函數,函數沒有返回值–>value是undefined。
奇怪的問題增加了。 不是說執行到yield前邊碼?怎麼返回值是yield後邊的東西哦。
看下圖:let y = yield (()=>console.log(y))()
拋出錯誤說我沒聲明y。這側面證明了。調用一個next方法,執行了yield後邊的代碼,但是沒執行yield前邊的代碼。
所以內部執行狀態應該是這樣的:遇到yield之後,會執行yield右邊緊跟着的內容,不執行yield左邊的內容,也不會執行yield下一行的內容。
yield就是一堵牆,牆右邊的和前邊的屬於一個世界,牆左邊和下邊的是一個世界。
第一個yield後邊無法輸出y,但是第二個yield就可輸出y了。也印證了我上邊說的話。
但是注意到沒,可以輸出y了,但是y是undefined,有沒有辦法讓它不是undefined?
當然可以,其實next方法可以傳值,用下邊的代碼實驗一下
function *foo() {
var state1 = yield 'hello';
console.log(state1)
var state2 = yield 'world';
console.log(state2)
}
let f = foo()
f.next('111')
f.next('222')
f.next(333)
神奇的事情發生了。輸出了222,333。也就是說輸出了第二個和第三個next的值。
其實仔細回想下,也沒有很神奇,都說了一牆之隔,第一個next只管到第一個yield的前邊和右邊,管不到左邊的state1的,更不用說給它賦值了。所以某個yield左邊的值,需要下一個next傳入參數。
一個有趣的題目
輸出兩行內容,第一行是"Do you love JavaScript?",第二行是"JavaScript loves you back ❤️"。
function *startGame() {
const answer = yield "Do you love JavaScript?";
if (answer !== "Yes") {
return "Oh wow... Guess we're gone here";
}
return "JavaScript loves you back ❤️";
}
const game = startGame();
答案:
console.log( game.next().value );
// Do you love JavaScript?
console.log(game.next('Yes').value);
// JavaScript loves you back ❤️