異步編程方案:(很實用滴)javascript
Generator 函數是一個普通函數,可是有兩個特徵。一是,function
關鍵字與函數名之間有一個星號;二是,函數體內部使用yield
表達式,定義不一樣的內部狀態(yield
在英語裏的意思就是「產出」)。java
每次調用next
方法,內部指針就從函數頭部或上一次停下來的地方開始執行,直到遇到下一個yield
表達式(或return
語句)爲止。換言之,Generator 函數是分段執行的,yield
表達式是暫停執行的標記,而next
方法能夠恢復執行。編程
yield
表達式只能用在 Generator 函數裏面,用在其餘地方都會報錯。數據結構
var arr = [1, [[2, 3], 4], [5, 6]];
var flat = function* (a) {
a.forEach(function (item) {
if (typeof item !== 'number') {
yield* flat(item);
} else {
yield item;
}
});
};
for (var f of flat(arr)){
console.log(f);
}
由於是在forEach中(forEach 自己是一個函數,因此會報錯)異步
yield
表達式若是用在另外一個表達式之中,必須放在圓括號裏面。ide
function* demo() { console.log('Hello' + yield); // SyntaxError console.log('Hello' + yield 123); // SyntaxError console.log('Hello' + (yield)); // OK console.log('Hello' + (yield 123)); // OK }
yield
表達式用做函數參數或放在賦值表達式的右邊,能夠不加括號。函數式編程
function* demo() { foo(yield 'a', yield 'b'); // OK let input = yield; // OK }
next方法參數:next方法能夠傳入一個參數,這個參數會被當作上一次yield執行時的初始值(若是不傳參數,默認是undefined)異步編程
function* foo(x) { var y = 2 * (yield (x + 1)); var z = yield (y / 3); return (x + y + z); } var a = foo(5); a.next() // Object{value:6, done:false} a.next() // Object{value:NaN, done:false} a.next() // Object{value:NaN, done:true} var b = foo(5); b.next() // { value:6, done:false } b.next(12) // { value:8, done:false } b.next(13) // { value:42, done:true }
主要看第一個實例(b),第一次調用next這次yield 後的值爲6,第二次執行next時,此時設置yield爲12,也就是上面我標紅的地方,(yield (x + 1)) = 12, 因此y = 24,到第二行yield 時,此時yield 回傳爲8,當調用第三次next方法時,設置值爲13,標紅的地方 yield (y / 3) = 13, 因此z值爲13,而後到達return ,此時 上面執行結果就是,x 爲5,y爲24,z爲13,相加獲得42。函數
注意若是yield 後面是一個表達式,那麼初始值表明的是這個表達式的值,也就是yield緊跟着的。而上面的實例a由於在第二次調用next方法的時候沒有傳參,因此yield 的值默認爲undefined,因此值爲NaNthis
for...of : 由於Generator 運行時會生成iterator,因此能夠直接遍歷
function* foo() { yield 1; yield 2; yield 3; yield 4; yield 5; return 6; } for (let v of foo()) { console.log(v); } // 1 2 3 4 5
遍歷對象:
function* objectEntries(obj) { let propKeys = Reflect.ownKeys(obj);// 獲取對象的keys for (let propKey of propKeys) {// 循環執行,返回key與value yield [propKey, obj[propKey]]; } } let jane = { first: 'Jane', last: 'Doe' }; for (let [key, value] of objectEntries(jane)) { console.log(`${key}: ${value}`); }
由於自己就會生成一個iterator 對象,因此能夠直接賦值到對象的Symbol.iterator接口上
function* objectEntries() {
let propKeys = Object.keys(this);
for (let propKey of propKeys) {
yield [propKey, this[propKey]];
}
}
let jane = { first: 'Jane', last: 'Doe' };
jane[Symbol.iterator] = objectEntries;
for (let [key, value] of jane) {
console.log(`${key}: ${value}`);
}
// first: Jane
// last: Doe
x.throw:拋出異常(記得用try catch捕獲)
var g = function* () { try { yield; } catch (e) { console.log('內部捕獲', e); } }; var i = g(); i.next(); try { i.throw('a'); i.throw('b'); } catch (e) { console.log('外部捕獲', e); } // 內部捕獲 a // 外部捕獲 b
catch只會捕獲一次,而且返回捕獲後下一次的yield 值
若是yield 都在try catch中,那麼不會返回。
沒有返回2,由於yield 2 在try catch內(而且不會影響下一次的遍歷上例中在錯誤後繼續遍歷,輸出了4)。
Generator 函數體外拋出的錯誤,能夠在函數體內捕獲;反過來,Generator 函數體內拋出的錯誤,也能夠被函數體外的catch
捕獲。
一旦 Generator 執行過程當中拋出錯誤,且沒有被內部捕獲,就不會再執行下去了。若是此後還調用next
方法,將返回一個value
屬性等於undefined
、done
屬性等於true
的對象,即 JavaScript 引擎認爲這個 Generator 已經運行結束了。
return:終結遍歷Generator
function* gen() { yield 1; yield 2; yield 3; } var g = gen(); g.next() // { value: 1, done: false } g.return('foo') // { value: "foo", done: true } g.next() // { value: undefined, done: true }
yield* 表達式:在 Generator 函數內部,調用另外一個 Generator 函數。
對比:
function* bar() { yield 'x'; yield* foo(); yield 'y'; } // 等同於 function* bar() { yield 'x'; yield 'a'; yield 'b'; yield 'y'; } // 等同於 function* bar() { yield 'x'; for (let v of foo()) { yield v; } yield 'y'; } for (let v of bar()){ console.log(v); } // "x" // "a" // "b" // "y"
另外由於Generator 會生成一個遍歷器對象,因此,會有下面這樣的不一樣
function* inner() { yield 'hello!'; } function* outer1() { yield 'open'; yield inner(); yield 'close'; } var gen = outer1() gen.next().value // "open" gen.next().value // 返回一個遍歷器對象 gen.next().value // "close" function* outer2() { yield 'open' yield* inner() yield 'close' } var gen = outer2() gen.next().value // "open" gen.next().value // "hello!" gen.next().value // "close"
out1 在執行到第二行的時候返回的是一個遍歷器對象。
任何數據結構只要有 Iterator 接口,就能夠被yield*
遍歷。
yield*
後面的 Generator 函數(沒有return
語句時),等同於在 Generator 函數內部,部署一個for...of
循環。(若是有return,那麼return的值不會被for ...of 偵測到,可是能夠做爲想代理它的Generator 函數返回數據。)
function* concat(iter1, iter2) { yield* iter1; yield* iter2; } // 等同於 function* concat(iter1, iter2) { for (var value of iter1) { yield value; } for (var value of iter2) { yield value; } }
function* foo() { yield 2; yield 3; return "foo"; } function* bar() { yield 1; var v = yield* foo(); console.log("v: " + v); yield 4; } var it = bar(); it.next() // {value: 1, done: false} it.next() // {value: 2, done: false} it.next() // {value: 3, done: false} it.next(); // "v: foo" // {value: 4, done: false} it.next() // {value: undefined, done: true}
對象屬性:
let obj = { * myGeneratorMethod() { ··· } };
let obj = { myGeneratorMethod: function* () { // ··· } };
協程:相比線程,因爲 JavaScript 是單線程語言,只能保持一個調用棧。引入協程之後,每一個任務能夠保持本身的調用棧。這樣作的最大好處,就是拋出錯誤的時候,能夠找到原始的調用棧。不至於像異步操做的回調函數那樣,一旦出錯,原始的調用棧早就結束。
若是將 Generator 函數看成協程,徹底能夠將多個須要互相協做的任務寫成 Generator 函數,它們之間使用yield
表達式交換控制權。同一時間能夠有多個線程處於運行狀態,可是運行的協程只能有一個,其餘協程都處於暫停狀態。此外,普通的線程是搶先式的,到底哪一個線程優先獲得資源,必須由運行環境決定,可是協程是合做式的,執行權由協程本身分配。
(也就是說能夠本身控制)。
異步操做同步化表達:Generator 函數的一個重要實際意義就是用來處理異步操做,改寫回調函數。
function* loadUI() { showLoadingScreen(); yield loadUIDataAsynchronously(); hideLoadingScreen(); } var loader = loadUI(); // 加載UI loader.next() // 卸載UI loader.next()
不用回調函數一個一個套了,看着多清晰~~
控制流:
通常若是異步操做,咱們之前會這樣作,一直套
step1(function (value1) { step2(value1, function(value2) { step3(value2, function(value3) { step4(value3, function(value4) { // Do something with value4 }); }); }); });
Promise寫法:
Promise.resolve(step1) .then(step2) .then(step3) .then(step4) .then(function (value4) { // Do something with value4 }, function (error) { // Handle any error from step1 through step4 }) .done();
已經很清晰了。
function* longRunningTask(value1) { try { var value2 = yield step1(value1); var value3 = yield step2(value2); var value4 = yield step3(value3); var value5 = yield step4(value4); // Do something with value4 } catch (e) { // Handle any error from step1 through step4 } }
scheduler(longRunningTask(initialValue));
function scheduler(task) {
var taskObj = task.next(task.value);
// 若是Generator函數未結束,就繼續調用
if (!taskObj.done) { task.value = taskObj.value scheduler(task); }
}
更加趨於函數式編程。