再讀Generator和Co源碼

之前看過的內容,覺得忘得差不多,近來抽閑又看了一次,果真書讀百遍其義自見

Generator的實行

Generator函數可以完成函數表裡的數據交流實行權交流

從第一次挪用next最先,從函數頭部最先實行,實行到第一個yield語句時,把實行權交出到函數外部,並返回該yield語句右值,同時在此處停息函數

鄙人一次挪用next時刻(可以通報參數),把實行權返還給函數內部,同時把參數賦值給上一次停息的yield語句的左值,並從該行到最先實行到下一個yield前,並一向輪迴該歷程

須要注重的是,yield語句的左值,不能由右值賦值,如 let a = yield 3a 的值並不等於3,a 的只能由函數外部挪用next時傳入的參數賦值。


function test() {
    return 3;
}

function* gen(){
    console.log(0);
    
    let yield1 = yield 1;
    console.log('yield1 value: ', yield1);// yield1: 2
    
    let yield2 = yield test();
    console.log('yield2 value: ', yield2);// yield2: 4
    
    return 3;
}

let gen1 = gen();

let next1 = gen1.next();
console.log('next1 value: ', next1);// next: { value: 1, done: false }

let next2 = gen1.next(2);
console.log('next2 value: ', next2);// next: { value: 3, done: false }

let next3 = gen1.next(4);
console.log('next3 value: ', next3);// next: { value: undefined, done: true }

第一次挪用

  • 從函數頂部最先往下實行,所以起首輸出 console.log(0)
  • 然後實行 yield1 = yield 1,此時會把表達式右值返回, 即返回 1
  • 所以此時 next1 = {value: 1, done: false}, 接着輸出 next1
  • gen函數內部在yield1 = yield 1停息

第二次挪用

  • 從函數內部 yield1 = yield 1 最先實行
  • 注重: 與第一次挪用差別,此次挪用傳入了參數2, 第一次挪用已實行了該yield語句,所以並不會返回右值,而是會舉行賦值操縱,把傳入的參數 2 賦給 yield1
  • 接着實行 console.log('yield1 value: ', yield1), 此時yield1 = 2
  • 然後實行 yield2 = yield test(), 此時會把表達式右值返回, 即返回 3
  • 所以此時 next2 = {value: 3, done: false}, 接着輸出 next2
  • gen函數內部在yield2 = yield test()停息

第三次挪用

  • 從函數內部 yield2 = yield test() 最先實行
  • 注重: 傳入了參數4, 舉行賦值操縱,此時yield2 = 4
  • 接着實行 console.log('yield2 value: ', yield2), 此時的 yield2 值為4
  • 由於函數內部已沒有yield語句,所以一向實行實行到函數尾部return 5
  • 所以末了 next3 = {value: 5, done: true}, 接着輸出 next2
  • 至此函數實行終了

我們發明Generator函數的實行就是一個輪迴挪用next的歷程,天然的想到運用遞返來完成自動實行

function* gen() {
  let a = yield 1;
  let b = yield 2;
  let c = yield 3;
}

var g = gen();
var res = g.next();

while(!res.done){
  console.log(res.value);
  res = g.next();
}

最簡樸的幾行代碼,就完成了Generator的”自動實行”,但有一個致命的瑕玷,代碼里如果有一步異步操縱,而且下一步的操縱依靠上一步的效果才實行,如許的代碼就會失足,沒法實行,代碼以下

function* gen() {
  let file1 = yield fs.readFile('a', () => {});
  let file2 = yield fs.readFile(file1.name, () => {});
}

var g = gen();
var res = g.next();

// 異步操縱,實行file2的yield時
// file1的值為undefined
while(!res.done){
  res = g.next(res.value);
}

這就非常為難了…運用Generator的一個初志就是為了防止多層次的回調,寫出同步代碼,而我們如今又卡在了回調上,所以須要運用Thunk函數

函數Thunk化

開闢中多半狀況都不會零丁運用Thunk函數,然則把Thunk和Generator連繫在一起運用時,就會發作巧妙的化學反應,可以用來完成Generator函數的自動實行。

Thunk化用一句話總結就是,將一個具有多個參數且有包括一個回調函數的函數轉換成一個只接收回調函數作為參數的單參數函數,附一段網上的完成

const Thunk = function(fn) {
  return function (...args) {
    return function (callback) {
      return fn.call(this, ...args, callback);
    }
  };
};

詳細道理不多贅述,根據個人明白,函數Thunk化,就是把帶有回調函數的函數拆分為兩步實行

// 一般函數
function func(a, b, callback){
  const sum = a + b;
  callback(sum);
}
// 一般挪用
func(1, 2, alert);

// 對函數舉行Thunk化
const ft = thunkify(func);
// Thunk化函數挪用
ft(1, 2)(alert);

包括異步操縱的例子,在實行fs.readFile(fileName)這第一步操縱值以後,數據已拿到,然則不對數據舉行操縱,而是在第二步的(err, data) => {}回調函數中舉行數據操縱

let fs = require('fs');
// 一般版本的readFile
fs.readFile(fileName, (err, data) => {});

// Thunk版本的readFile
fs.readFile(fileName)((err, data) => {});

Generator的自動實行

現在連繫ThunkPromise都可以完成

Generator + Thunk

上面報錯的例子,把readFileThunk化以後,題目就可以獲得解決,

let thunkify = require('thunkify');
let readFileThunk = thunkify(fs.readFile);

function* gen() {
  let file1 = yield readFileThunk('a');
  let file2 = yield readFileThunk(file1.name);
}

var g = gen();
var r1 = g.next();

r1.value(function (err, data) { // 這個回調就是readFileThunk('a')的回調
  var r2 = g.next(data);  // 等價於file1 = data;
  r2.value(function (err, data) {
    if (err) throw err;
    g.next(data);
  });
});

實行next后返回對象中的value,不再是一個簡樸的值,而是一個回調函數,即readFileThunk的第二步操縱,在這個回調函數里,可以獲得異步操縱的效果,更主要的是可以在這個回調函數中繼承挪用next,把函數的實行權返還給gen函數內部,同時把file1的值經由過程next的參數通報進去,全部遞歸就可以一向運轉。

Generator + Promise

相沿上面的例子,把readFile包裝成一個Promise對象


const readFile = function (fileName){
  return new Promise(function (resolve, reject){
    fs.readFile(fileName, function(error, data){
      if (error) return reject(error);
      resolve(data);
    });
  });
};

function* gen() {
  let file1 = yield readFileThunk('a');
  let file2 = yield readFileThunk(file1.name);
}

var g = gen();
var r1 = g.next();

r1.value.then(function (data) { // 這個回調就是resolve(data)
  var r2 = g.next(data);  // 等價於file1 = data;
  r2.value.then(function ( data) {
    if (err) throw err;
    g.next(data);
  });
});

經由過程在then里實行回調函數,獵取到上一步操縱的效果和交回實行權,並把值通報回gen函數內部,完成了遞歸實行

進一步封裝,可以獲得以下的代碼

let Bluebird = require('bluebird');
let readFileThunk = Bluebird(fs.readFile);

function run(fn) {
  const gen = fn();
  function next(err, data) {
    const result = gen.next(data);
    if (result.done) {
      result.value;
    } else {
      result.value.then((data) => {
        next(data);
      });
    }
  }
  
  // 遞歸實行
  next();
}

run(function* g() {
  let file1 = yield readFileThunk('a');
  let file2 = yield readFileThunk(file1.name);
});
    原文作者:灰羊忘
    原文地址: https://segmentfault.com/a/1190000015254832
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞