從C#到TypeScript - Generator

從C#到TypeScript - Generator

上篇講了PromisePromise的執行須要不停的調用then,雖然比callback要好些,但也顯得累贅。因此ES6裏添加了Generator來作流程控制,能夠更直觀的執行Promise,但終級方案仍是ES7議案中的async await
固然async await本質上也仍是Generator,能夠算是Generator的語法糖。
因此這篇先來看下Generator.javascript

Generator語法

先來看個例子:java

function* getAsync(id: string){ yield 'id'; yield id; return 'finish'; } let p = getAsync('123'); console.info(p.next()); console.info(p.next()); console.info(p.next());

先看下和普通函數的區別,function後面多了一個*,變成了function*,函數體用到了yield,這個你們比較熟悉,C#也有,返回可枚舉集合有時會用到。
在ES6裏yield一樣表示返回一個迭代器,因此用到的時候會用next()來順序執行返回的迭代器函數。
上面代碼返回的結果以下:git

{ value: 'id', done: false } { value: '123', done: false } { value: 'finish', done: true }

能夠看到next()的結果是一個對象,value表示yield的結果,done表示是否真正執行完。
因此看到最後return了finishdone就變成true了,若是這時再繼續執行next()獲得的結果是{ value: undefined, done: true }.github

Generator原理和使用

Generator實際上是ES6對協程的一種實現,即在函數執行過程當中容許保存上下文同時暫停執行當前函數轉而去執行其餘代碼,過段時間後達到條件時繼續以上下文執行函數後面內容。
所謂協程其實能夠看作是比線程更小的執行單位,一個線程能夠有多個協程,協程也會有本身的調用棧,不過一個線程裏同一時間只能有一個協程在執行。
並且線程是資源搶佔式的,而協程則是合做式的,怎樣執行是由協程本身決定。
因爲JavaScript是單線程語言,自己就是一個不停循環的執行器,因此它的協程是比較簡單的,線程和協程關係是 1:N。
一樣是基於協程goroutine的go語言實現的是 M:N,要同時協調多個線程和協程,複雜得多。
Generator中碰到yield時會暫停執行後面代碼,碰到有next()時再繼續執行下面部分。typescript

當函數符合Generator語法時,直接執行時返回的不是一個確切的結果,而是一個函數迭代器,所以也能夠用for...of來遍歷,遍歷時碰到結果done爲true則中止。json

function* getAsync(id: string){ yield 'id'; yield id; return 'finish'; } let p = getAsync('123'); for(let id of p){ console.info(id); }

打印的結果是:promise

id 123

由於最後一個finishdone是true,因此for...of中止遍歷,最後一個就不會打印出來。
另外,Generatornext()是能夠帶參數的,app

function* calc(num: number){ let count = yield 1 + num; return count + 1; } let p = calc(2); console.info(p.next().value); // 3 console.info(p.next().value); // NaN //console.info(p.next(3).value); // 4

上面的代碼第一個輸出是yield 1 + num的結果,yield 1返回1,加上傳進來的2,結果是3.
繼續輸出第二個,按正常想法,應該輸出3,可是因爲yield 1是上一輪計算的,這輪碰到上一輪的yield時返回的老是undefined
這就致使yield 1返回undefined,undefined + num返回的是NaN,count + 1也仍是NaN,因此輸出是NaN
註釋掉第二個,使用第三個就能夠返回預期的值,第三個把上一次的結果3用next(3)傳進去,因此能夠獲得正確結果。
若是想一次調用全部,能夠用此次方式來遞歸調用:異步

let curr = p.next(); while(!curr.done){ console.info(curr.value); curr = p.next(curr.value); } console.info(curr.value); // 最終結果

Generator能夠配合Promise來更直觀的完成異步操做。async

function delay(): Promise<void>{ return new Promise<void>((resolve, reject)=>{setTimeout(()=>resolve(), 2000)}); } function* run(){ console.info('start'); yield delay(); console.info('finish'); } let generator = run(); generator.next().value.then(()=>generator.next());

run這個函數來看,從上到下執行是很好理解的,先輸出'start',等待2秒,再輸出'finish'。
只是執行時須要不停的使用then,好在TJ大神寫了CO模塊,能夠方便的執行這種函數,把Generator函數傳給co便可。

co(run).then(()=>console.info('success'));

co的實現原理能夠看下它的核心代碼:

function co(gen) { var ctx = this; var args = slice.call(arguments, 1); return new Promise(function(resolve, reject) { if (typeof gen === 'function') gen = gen.apply(ctx, args); if (!gen || typeof gen.next !== 'function') return resolve(gen); onFulfilled(); //最主要就是這個函數,遞歸執行next()和then() function onFulfilled(res) { var ret; try { ret = gen.next(res); // next(), res是上一輪的結果 } catch (e) { return reject(e); } next(ret); // 裏面調用then,並再次調用onFulfilled()實現遞歸 return null; } function onRejected(err) { // 處理失敗的狀況 var ret; try { ret = gen.throw(err); } catch (e) { return reject(e); } next(ret); } function next(ret) { if (ret.done) return resolve(ret.value); // done是true的話表示完成,結束遞歸 var value = toPromise.call(ctx, ret.value); if (value && isPromise(value)) return value.then(onFulfilled, onRejected); //遞歸onFulfilled return onRejected(new TypeError('You may only yield a function, promise, generator, array, or object, ' + 'but the following object was passed: "' + String(ret.value) + '"')); } }); }

能夠看到co的核心代碼和我上面寫的遞歸調用Generator函數的本質是同樣的,不斷調用下一個Promise,直到done爲true。

縱使有co這個庫,可是使用起來仍是略有不爽,下篇就輪到async await出場,前面這兩篇都是爲了更好的理解下一篇。

相關文章
相關標籤/搜索