作者:麥樂
來源:恆生LIGHT雲社區
Generator函數基本用法
function* helloWorldGenerator() {
yield 'hello';
yield 'world';
return 'ending';
}
var hw = helloWorldGenerator();
Generator函數調用後生成的就是一個迭代器對象,可以通過調用迭代器的next方法,控制函數內部代碼的執行。
hw.next()
// { value: 'hello', done: false }
hw.next()
// { value: 'world', done: false }
hw.next()
// { value: 'ending', done: true }
hw.next()
// { value: undefined, done: true }
Generator函數遇到yield,可以在生成器函數內部暫停代碼的執行使其掛起。在可迭代對象上調用next()方法可以使代碼從暫停的位置開始繼續往下執行。
先來了解一下什麼是可迭代對象?
可迭代對象
要成為可迭代 對象, 一個對象必須實現 <strong>@@iterator</strong> 方法。這意味着對象(或者它原型鏈上的某個對象)必須有一個鍵為 @@iterator 的屬性,可通過常量 Symbol.iterator 訪問該屬性:
[Symbol.iterator] 一個無參數的函數,其返回值為一個符合迭代器協議的對象。
let someString = "hi";
typeof someString[Symbol.iterator];
let iterator = someString[Symbol.iterator]();
iterator + ""; // "[object String Iterator]"
iterator.next(); // { value: "h", done: false }
iterator.next(); // { value: "i", done: false }
iterator.next(); // { value: undefined, done: true }
再來看一下,掛起是怎麼回?有一個新的名詞“協程”。
什麼是協程?
協程(Coroutines)是一種比線程更加輕量級的存在,正如一個進程可以擁有多個線程一樣,一個線程可以擁有多個協程。
協程不是被操作系統內核所管理的,而是完全由程序所控制,也就是在用户態執行。這樣帶來的好處是性能大幅度的提升,因為不會像線程切換那樣消耗資源。
協程不是進程也不是線程,而是一個特殊的函數,這個函數可以在某個地方掛起,並且可以重新在掛起處外繼續運行。所以説,協程與進程、線程相比並不是一個維度的概念。
Generator 函數原理
Generator 函數是協程在 ES6 的實現,最大特點就是可以交出函數的執行權(即暫停執行)。
總結下來就是:
- 一個線程存在多個協程
- Generator函數是協程在ES6的實現
- Yield掛起協程(交給其它協程),next喚起協程
講到這裏,應該會對Generator函數有一個重新的認識吧。在實際的開發中,直接使用Generator函數的場景並不常見,因為它只能通過手動調用next方法實現函數內部代碼的順序執行。如果想象很好是使用它,可以為Generator函數實現一個自動執行神器。
自動執行的Generator函數
可以根據g.next()的返回值{value: '', done: false}中done的值讓Generator函數遞歸自執行:
function run(generator) {
var g = generator();
var next = function() {
var obj = g.next()
console.log(obj)
if(!obj.done) {
next()
}
}
next()
}
run(helloWorldGenerator)
這樣寫能實現自執行功能,但是 不能保證執行順序。模擬兩個異步請求:
function sleep1() {
return new Promise((resolve) => {
setTimeout(() => {
console.log('sleep1')
resolve(1)
}, 1000)
})
}
function sleep2() {
return new Promise((resolve) => {
setTimeout(() => {
console.log('sleep2')
resolve(2)
}, 1000)
})
}
修改函數
function* helloWorldGenerator() {
yield sleep1();
console.log(1);
yield sleep2();
console.log(2);
}
執行 run(helloWorldGenerator)看一下打印順序:
異步函數還是在同步代碼執行完以後執行的,如果想要實現異步代碼也能按照順序執行,可以對代碼進一步優化:
function run(generator) {
var g = generator();
var next = function() {
var obj = g.next();
console.log(obj)
if(obj.done) return;
// 如果yield後面返回值不是promise,可以使用Promise.resolve包裹一下,防止報錯
Promise.resolve(obj.value).then(() => {next()})
}
next()
}
如果説sleep1是一個網絡請求的話,在yield後面就可以拿到請求返回的數據,看起來像是一種更優雅的異步問題解決方案。
如果想要拿到sleep函數resolve的值,也是可以實現的。
// 修改函數 變量接收yield語句返回結果
function* helloWorldGenerator() {
var a = yield sleep1();
console.log(a);
var b = yield sleep2();
console.log(b);
}
// g.next(v); 傳遞結果值
function run(generator) {
var g = generator();
var next = function(v) {
var obj = g.next(v);
console.log(obj)
if(obj.done) return;
// 如果yield後面返回值不是promise,可以使用Promise.resolve包裹一下,防止報錯
Promise.resolve(obj.value).then((v) => {next(v)})
}
next()
}
你會看到和上面一樣的打印結果。
仔細看上面實現方式跟async await很相似,實際上這就是async await的原理。
async await
async await本質上就是結合promise實現的一個自執行Generator函數。將 Generator 函數的星號(*)替換成async,將yield替換成await,僅此而已。
更詳細的代碼如下,感興趣的同學可以深入瞭解一下:
// async函數就是將 Generator 函數的星號(*)替換成async,將yield替換成await,僅此而已
// async函數返回一個 Promise 對象,可以使用then方法添加回調函數。當函數執行的時候,一旦遇到await就會先返回,等到異步操作完成,再接着執行函數體內後面的語句
// Generator 函數的執行必須靠執行器,所以才有了co模塊,而async函數自帶執行器和普通函數一樣執行
// async函數對 Generator 函數的改進:
/*
(1)內置執行器
(2)更好的語義
(3)更廣的適用性
(4)返回值是 Promise
*/
// async 函數的實現原理,就是將 Generator 函數和自動執行器,包裝在一個函數裏。
async function fn(args) {
// ...
}
// 等同於 裏面的await 換成 yield
function fn(args) {
return spawn(function*() {
// ...
});
}
function spawn(genF) {
return new Promise(function(resolve, reject) {
const gen = genF();
function step(nextF) {
let next;
try {
next = nextF();
} catch (e) {
return reject(e);
}
if (next.done) {
return resolve(next.value); // 這也是為什麼 不使用try catch的話,異常異步請求後面的代碼不再執行的原因,從這裏不再繼續調用nex()方法了。
}
Promise.resolve(next.value).then(function(v) {
step(function() {
return gen.next(v);
});
}, function(e) {
step(function() {
return gen.throw(e); // 這裏可以解釋為什麼async 函數需要使用try catch來捕獲異常,生成器函數的throw,會讓代碼到catch裏面
});
});
}
step(function() {
return gen.next(undefined);
});
});
}
想向技術大佬們多多取經?開發中遇到的問題何處探討?如何獲取金融科技海量資源?
恆生LIGHT雲社區,由恆生電子搭建的金融科技專業社區平台,分享實用技術乾貨、資源數據、金融科技行業趨勢,擁抱所有金融開發者。
掃描下方小程序二維碼,加入我們!