初探ES6特性-Generator
Javascript函式的特性就是一旦開始執行,就無法被停止。那如果我想在中間停下來,先去執行其他的支線任務,等做完再回來做主線任務,這樣我不就辦不到了嗎 ? 如下面的範例,雖然我有設一毫秒後才執行印出log的function,但下面的function仍然必須等執行完畢後才將主控權交還給setTimeout。(這故事告訴我們,VIP也是要乖乖排隊…)
setTimeout(function () {
console.log("我是VIP,讓我插一下隊啦", (new Date).getMilliseconds());
}, 1);function f() {
for (var i = 1; i <= 100; i++) {
console.log(`第${i}位進場`, (new Date).getMilliseconds());
}
}
f();
執行結果如下
第1位進場 460
第2位進場 462
第3位進場 463
…
第98位進場 465
第99位進場 465
第100位進場 465
我是VIP,讓我插一下隊啦 466
ES6 Generator
ES6 Generator容許執行到一半時,可以暫停多次,直到完成後再繼續接下來的工作。可以把JS寫法看起來像是同步的寫法。首先,我們先介紹一下Generator的基本語法,
- Generator function:可以包含多個yield來達到暫停和繼續的目的。如:function* f(…) {…}。和一般的function不同的是function旁會多一個星號。
- Generator object:如var it = f(),it得到並不是f()回傳的結果,而是得到一個generator object,當然他也繼承iterator的特性,可以執行類似loop等操作。iterator物件實行了iterator介面,所以有iterator的特性,回傳的format為{ value: Any, done: Boolean }。
- yield:function中包含yield時,程式執行中遇到yield時,可以暫時把主控權給交出去,以達到類似同步的效果。當然一個function中可以包含多個yield來達到暫停和繼續的目的。yield很類似return的效果,但特別的下一次進來時會從上次中斷的地方繼續執行。
- next:在function外,可以用it.next()來控制function的執行順序,當我們呼叫it.next()時,會回傳一個帶有value和done的物件。value代表function回傳的值,而done若為false,代表iterator尚未跑到最後一筆。在next中也可加入變數,如it.next(v),v就是下一次要傳回給generator的參數。
讓我們來看個例子,來驗證我們上述所提到genarator的特性,
function* f(n) {
yield n;
yield "Anderson";
yield n + 1;
return n + 2;
}
var it = f(1);
console.log(it.next().value);
// 1
console.log(it.next().value);
// Anderson
console.log(it.next());
// { value: 2, done: false }
console.log(it.next());
// { value: 3, done: true }for (var v of f(100)) { console.log(v); }
// 100
// Anderson
// 101
特別的是最後一個迴圈並不會印出結果102,因為generator遇到return就結束迴圈,所以也不會回傳iterator的結果。
再來看下一個建立帳號的例子,
function* createAccount(n) {
var x = yield n;
console.log("帳號:", x);
var y = yield n + 1;
console.log("密碼:", y);
}
var step = 1;
var it = createAccount(step);
console.log(it.next("init").value);
console.log(it.next("Anderson").value);
console.log(it.next("12345"));// Result:
// 1 (目前還沒有yield,所以init的值仍無法指派)
// 帳號: Anderson (Anderson指派給x)
// 2
// 密碼: 12345 (12345指派給y)
// { value: undefined, done: true }
generator function也可以在function中另一個generator function,範例如下,
function* sendWelcomMsg() {
yield 'Welcome home';
}function* getUserInfo() {
yield 'account: Anderson';
yield 'phone: 123-4567';
yield *sendWelcomMsg();
}for (var v of getUserInfo()) {
console.log(v);
}// Result:
// account: Anderson
// phone: 123-4567
// Welcome home
再來看一個模擬使用Ajax去呼叫API,會回傳url和所帶入的參數的非同步應用,如果用一般的function來實作的結果如下,
function callApi(url, cb) {
setTimeout(() => {
var obj = { url: url, params: url.split('/')[1] };
cb(JSON.stringify(obj));
}, 1000);
}callApi("user/1", v => {
console.log(v);
callApi("friends/1", r => {
console.log(r);
});
});// Result:
// {"url":"user/1","params":"1"} (隔一秒後)
// {"url":"friends/1","params":"1"} (隔一秒後)
如果我們頻繁呼叫API,且每次都要從API拿回資料做下一次的參數,上面就會寫出如callback 地獄的寫法了,若改為用generator實做呢?
function callApi(url, cb) {
setTimeout(() => {
var obj = { url: url, params: url.split('/')[1]};
cb(JSON.stringify(obj));
}, 1000);
}function request(url){
callApi(url, res => {
it.next(res);
})
}function* main() {
var result = yield request("user/1");
var data = JSON.parse(result);
console.log(data); result = yield request("friends/1");
data = JSON.parse(result);
console.log(data);
}var it = main();
it.next();// Result:
// { url: 'user/1', params: '1' }
// { url: 'friends/1', params: '1' }
在main這個Generator function寫出看起來像是同步(synchronous)的程式碼了,雖然額外多拉出一個function,但是我們把callApi和request拆成只負責某件事的function。優點如下,
- 不會寫出像callback 內縮地獄的寫法。
- 回傳的結果也不用等到promise.then才能取得。
- 讓程式看起來像是同步的寫法。