生成器函式
在寫其他 js 程式碼時會經常用到 debugger
的東西,能夠讓當前執行的函式暫停住。生成器函式裡的 yield
關鍵詞也能使得函式暫停執行,同時還能用來做資料的輸入輸出(如果是 debugger
,只能手動去逐個 inspect 變數)。
yield
可以當作“斷點”。yield value
除了能當作斷點,還可以向外部輸出 value。
此外,yield
表示式本身也是可以有值的,也就是說還可以表示外部傳入的資料。只要外部在呼叫 next(inputValue)
時輸入一個值。
這就是為什麼在賦值語句裡,可以用 yield
作為右值(例如,在使用了 react-saga 的專案裡到處都是這種玩意兒)。
function* fn1(){
var data = yield "字串反轉器已啟動"
yield data.split("").reverse().join("")
}
// 建立 Generator 物件
var g = fn1()
// 執行函式,直到遇到第一個“斷點”(yield 處)
console.log(g.next())
// 上次停頓的位置是 var data = yield "字串反轉器已啟動"
// 因此可以在繼續執行前,使得 (yield "字串反轉器已啟動") 的值為 "Hello"
console.log(g.next("Hello"))
// 輸出:
// { value: `字串反轉器已啟動`, done: false }
// { value: `olleH`, done: false }
複製程式碼
Generator
正如前面的示例,呼叫一個 function*
生成器函式,會返回一個 Generator 物件(上文的 g
)。這個物件是可迭代物件(最常見的用法,即用於 for of
迴圈中)。
此外,可以把它當作一個控制器,控制著一個被 yield
打了斷點(而且還沒開始執行)的函式。
Generator 其具備下列方法:
next(inputValue?): { value, done }
- (從頭,或者從上次停止的位置)開始執行生成器函式體,直到遇到
yield
或者return
- 注意:此函式可以有一個可選的引數
inputValue
。可以在繼續執行生成器函式前,為上次停頓所在的 yield 表示式設定一個值。
- (從頭,或者從上次停止的位置)開始執行生成器函式體,直到遇到
throw(e): { value, done }
- 如果生成器函式沒開始執行,則等同於原地 throw
- 否則,在生成器函式體當前
yield
停頓的位置 throw 一個異常,然後繼續執行,直到遇到yield
、return
或者未被捕獲的異常(見下文)
return(value): {value, done}
- 強行終止(即使生成器函式還沒執行完畢),並指定一個 value 作為返回值
[Symbol.iterator]()
- 用於迭代協議的。效果同直接呼叫
next()
- 用於迭代協議的。效果同直接呼叫
yield*
和 yield
關鍵詞相比,多了一個星號。可以把 yield* another_iterable
當作以下程式碼語法糖:
for (let item of another_iterable) {
yield item;
}
複製程式碼
也就是說,在生成器函式裡, yield*
就是針對一個可迭代物件,把它的每一項逐個地 yield
出來。
有趣的例子:
yield* [1,2,3]
會把3個數字逐個 yield 出來。陣列是可迭代的。yield* "abcdefg"
會把這7個字母逐個 yield 出來。字串也是可迭代物件。yield* another_generator
相當於把另一個 Generator 的輸出,當作自己的輸出給一個個 yield 出去了。- 這個有趣的特性可以用來做攔截器之類的應用。
- 說到了串聯,如果是當前生成器函式想要利用另外一個生成器的返回值,直接呼叫
another_generator.next().value
就行了
生成器函式內的異常
由 next()
或者 throw(e)
可以讓生成器函式開始執行。在執行過程中,如果生成器函式裡遇到了未被捕獲的異常(可以是生成器內部自己產生的,或者由外部呼叫 throw(e)
塞進去的),那麼會在外部由 next()
或者 throw(e)
給 throw 出來
function* test(){
try {
console.log("inner: Hello")
console.log("inner: GET" + (yield "output1"))
} catch (err) {
console.log("inner: Caught", err)
}
console.log("inner: Done")
return "output 2"
}
var g = test()
console.log("outer: next: ", g.next("input 1"))
console.log("outer: throw: ", g.throw("err"))
console.log("outer: next: ", g.next("input 2"))
複製程式碼
上面的例子輸出如下。注意到由外部提供的 input1
不會被輸出,因為那是生成器函式還沒開始執行的時候傳進去的,能傳到哪裡?沒有任何的意義。
inner: Hello
outer: next: { value: `output1`, done: false }
inner: Caught err
inner: Done
outer: throw: { value: `output 2`, done: true }
outer: next: { value: undefined, done: true }
複製程式碼
安利
寫了一個 Markdown 編輯器元件,只要一個框,所見即所得,而且還全面相容 CodeMirror(一個很強大的程式碼編輯元件)
悄悄安利一下: laobubu.net/HyperMD/