理解 es5 開始有的 yield 和 es6 開始有的 aysnc/await

最近的業餘時間在看 js 相關的書, 也在極客時間上買了前端相關的專欄, 對於一個非 jser 的人來講, 時時會有一種感受: js 社區是真的激進和浮燥, 這幫規則的制定者彷佛曆來也不知道剋制爲什麼物. 有不少時候固有的東西是能夠處理好的, 可是恰恰喜歡人爲製造一些概念和語法糖, 人爲的建起一座又一座的高山, 彷佛你沒跨過就是個 "菜雞"前端

請原諒個人惡毒, 看《js 語言精粹》的時候, 這種感受很是的強烈. 做者是業內的大牛, 還制定了 json, 可是每一章還在最開頭引一句莎翁的話, "彷佛可有可無又暗藏哲理". 書中不少內容要表達的意思總有一種: 明明能夠將話說成 10 分讓一個普通人都能看得明白的, 卻偏不, 只說 6 分, 剩下的本身去悟, 有不少規則性的東西並不是是靠悟, 而是靠幾句話說清楚其本質就豁然開朗的.面試

換成之前, 會以爲這人是一座好大的高山要進行膜拜, 這幾年雖然本身技術依然不那麼好, 可是仍是喜歡去思考一些內在的東西, 更在努力一點一點把內心的權威崇拜給去掉, 再看到這些的時候, "..." 這幾個標點符號很容易印在腦子裏. 感受這不僅是一兩我的這樣, 極有多是整個 js 圈子都是這樣chrome

說回標題上來, 除了看書, 看專欄, 找資料, 好久都仍是沒有把 generator 和 async/await 給理解透, 因而本身試着整個梳理了一下運行的流程json

Generator

我先試着在 yield 後面不跟任何的東西, 能夠直接複製到控制檯輸出異步

function *f0(param) {
    console.log('n: ' + param);
    yield;
    console.log('i');
    let l = yield;
    console.log('l: ' + l);
}
let v0 = f0('p');
console.log(v0.next(1)); // 輸出  n: p  和  {value: undefined, done: false}
console.log('----');
console.log(v0.next(2)); // 輸出  i  和  {value: undefined, done: false}
console.log('----');
console.log(v0.next(3)); // 輸出  l: 3  和  {value: undefined, done: true}
console.log('----');
console.log(v0.next(4)); // 輸出 {value: undefined, done: true}
console.log('----');
console.log(v0.next(5)); // 輸出 {value: undefined, done: true}

在上面的基礎上給方法 return 值async

function *f1() {
    console.log('n');
    yield;
    console.log('i');
    let l = yield;
    console.log('l: ' + l);
    return '?';
}
let v1 = f1();
console.log(v1.next(1));     // 輸出  n  和  {value: undefined, done: false}
console.log('----');
console.log(v1.next(11));    // 輸出  i  和  {value: undefined, done: false}
console.log('----');
console.log(v1.next(111));   // 輸出  l: 111  和  {value: '?', done: true}
console.log('----');
console.log(v1.next(1111));  // 輸出 {value: undefined, done: true}
console.log('----');
console.log(v1.next(11111)); // 輸出 {value: undefined, done: true}

而後我試着在 yield 的後面加上內容firefox

function *f2(param) {
    console.log('0: ' + param);
    let f = yield 1;
    console.log('1: ' + f);
    let s = yield f + 2;
    console.log('2: ' + s);
    let t = yield (s + 3);
    console.log('3: ' + t);
    let fo = (yield s) + 4;
    console.log('4: ' + fo);
}
let v2 = f2('p');
console.log(v2.next('N')); // 輸出  0: p  和  {value: 1, done: false}
console.log('----');
console.log(v2.next('I')); // 輸出  1: I  和  {value: "I2", done: false}
console.log('----');
console.log(v2.next('L')); // 輸出  2: L  和  {value: "L3", done: false}
console.log('----');
console.log(v2.next('S')); // 輸出  3: S  和  {value: "L", done: false}
console.log('----');
console.log(v2.next('H')); // 輸出  4: H4  和  {value: undefined, done: true}
console.log('----');
console.log(v2.next('I')); // 輸出  {value: undefined, done: true}
console.log('----');
console.log(v2.next('T')); // 輸出  {value: undefined, done: true}

最後, 在上面的基礎上給方法 return 值code

function *f3() {
    console.log('0');
    let y1 = yield 1;
    console.log('1: ' + y1);
    let y2 = yield y1 + 2;
    console.log('2: ' + y2);
    let y3 = yield (y2 + 3);
    console.log('3: ' + y3);
    let y4 = (yield y3) + 4;
    console.log('4: ' + y4);
    return '??';
}
let v3 = f3();
console.log(v3.next('N')); // 輸出  0  和  {value: 1, done: false}
console.log('----');
console.log(v3.next('I')); // 輸出  1: I  和  {value: "I2", done: false}
console.log('----');
console.log(v3.next('L')); // 輸出  2: L  和  {value: "L3", done: false}
console.log('----');
console.log(v3.next('S')); // 輸出  3: S  和  {value: "S", done: false}
console.log('----');
console.log(v3.next('H')); // 輸出  4: H4  和  {value: "??", done: true}
console.log('----');
console.log(v3.next('I')); // 輸出  {value: undefined, done: true}
console.log('----');
console.log(v3.next('T')); // 輸出  {value: undefined, done: true}

大體上就清楚 yield 的運行邏輯了, 以上面的 f3 爲例, 對照上面的輸出來看, 它實際上是將一個方法分紅了這樣幾段來執行隊列

// 下面  五行一塊兒的豎線(|)  用一個大括號表示出來會更直觀一點
function *f3() {
    // 調用 next('N') 時運行的代碼
    console.log('0');
    let y1 = yield 1;
    return 1;                    // | 封裝成 {value: 1, done: false} 返回
                                 // |
                                 // | 這兩行等同於 let y1 = yield 1;
    // 調用 next('I') 時運行的代碼  // |
    let y1 = 'I';                // |
    console.log('1: ' + y1);
    return y1 + 2;               // | 封裝成 {value: "I2", done: false} 返回
                                 // |
                                 // | 這兩行等同於 let y2 = yield y1 + 2;
    // 調用 next('L') 時運行的代碼  // |
    let y2 = 'L';                // |
    console.log('2: ' + y2);
    return (y2 + 3);             // | 封裝成 {value: "L3", done: false} 返回
                                 // |
                                 // | 這兩行等同於 let y3 = yield (y2 + 3);
    // 調用 next('S') 時運行的代碼  // |
    let y3 = 'S';                // |
    console.log('3: ' + y3);
    return (y3);                 // | 封裝成 {value: "S", done: false} 返回
                                 // |
                                 // | 這兩行等同於 let y4 = (yield y3) + 4;
    // 調用 next('H') 時運行的代碼  // |
    let y4 = ('H') + 4;          // |
    console.log('4: ' + y4);
    return '??';                 // 封裝成 {value: "??", done: true} 返回
    
    // done 爲 true 以後, 再 next() 就都返回那一個了
}

再回頭想想就知道了, 第一次運行 next('N') 的時候, 傳進去的 N 是會被忽略的, 由於第一次 next() 傳的值沒有 yield 前面來接收. 再去看書也好, 看查到的文章也好, 第一次 next() 都是沒有傳過參數generator

感受 yield 就是爲了迭代而生的, 迭代徹底能夠就用 for 啊, 可是卻繞成這樣, 也不知道這是爲哪般! 就這還能新弄一個 for of 玩出花來, 由於每執行 next() 纔會執行那一段段, 還美其名曰 "我們終於能夠異步了"

async/await

再是 es7 開始有的這倆關鍵字, 看了一個大廠的面試題, 本身爲了加深對這兩個關鍵字的理解改了一下成下面這樣

async function async1() {
    console.log('A');
    console.log(await async2());
    return 'B';
}
async function async2() {
    console.log('C');
    return 'D';
}
console.log('E');
setTimeout(function() {
    console.log('F');
}, 0);
async1().then(function(r) {
    console.log(r);
});
new Promise(function(resolve, reject) {
    console.log('G');
    resolve();
}).then(function() {
    console.log('H');
});
console.log('I');

在 chrome 73.0.3683.75 底下的輸出是:

// 這個 undefined 的意思應該主要是用來分隔宏任務的, 也就是前面的主線和任務隊列是在一塊兒的
E  A  C  G  I  D  H  B  undefined  F

在 firefox 60.5.1 底下的輸出

// 這個 undefined 的意思應該只是用來分隔主線的, 任務隊列和宏任務在一塊兒了
E  A  C  G  I  undefined  H  D  B  F

在 opera 58.0.3135.107 底下的輸出是:

// 這個 undefined 應該跟 chrome 裏面是同樣的
E  A  C  G  I  H  D  B  undefined  F

明顯 D H B 是比較合理的. 在 firefox 和 opera 的實現中明顯是有問題的, 想像獲得, 低版本一點的 chrome 也多是後面的結果

還有像 var let const 這種一個簡單的賦值都能玩出這麼多花樣(固然, 這能夠說是歷史遺留問題致使的)

老實說, 我以爲這更可能是爲了: "別的語言有, 我們這麼前衛的語言固然應該要有!"

...

就這麼樣一門語言, 竟然能夠流行成如今這樣, 只能說這個世界是真奇妙

相關文章
相關標籤/搜索