关于yield

  • yield 表达式
    • 遇到yield表达式,就暂停执行后面的操作,并将紧跟在yield后面的那个表达式的值,作为返回的对象的value属性值。
    • 下一次调用next方法时,再继续往下执行,直到遇到下一个yield表达式。
    • 如果没有再遇到新的yield表达式,就一直运行到函数结束,直到return语句为止,并将return语句后面的表达式的值,作为返回的对象的value属性值。
    • 如果该函数没有return语句,则返回的对象的value属性值为undefined

eg.

function* helloWorldGenerator() {
  yield 'hello';
  yield 'world';
  return 'ending';
}

var hw = helloWorldGenerator();


hw.next()
// { value: 'hello', done: false }

hw.next()
// { value: 'world', done: false }

hw.next()
// { value: 'ending', done: true }

hw.next()
// { value: undefined, done: true }

  • next

yield表达式本身没有返回值,或者说总是返回undefinednext方法可以带一个参数,该参数就会被当作上一个yield表达式的返回值。

eg.

function* f() {
  for(var i = 0; true; i++) {
    var reset = yield i;
    if(reset) { i = -1; }
  }
}

var g = f();

g.next() // { value: 0, done: false }
g.next() // { value: 1, done: false }
g.next(true) // { value: 0, done: false }

more eg.

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 }

上面代码中,第二次运行next方法的时候不带参数,导致y的值等于2 * undefined(即NaN),除以3以后还是NaN,因此返回对象的value属性也等于NaN。第三次运行Next方法的时候不带参数,所以z等于undefined,返回对象的value属性等于5 + NaN + undefined,即NaN

如果向next方法提供参数,返回结果就完全不一样了。上面代码第一次调用b的next方法时,返回x+1的值6;第二次调用next方法,将上一次yield表达式的值设为12,因此y等于24,返回y / 3的值8;第三次调用next方法,将上一次yield表达式的值设为13,因此z等于13,这时x等于5,y等于24,所以return语句的值等于42。

注意,由于next方法的参数表示上一个yield表达式的返回值,所以在第一次使用next方法时,传递参数是无效的。V8 引擎直接忽略第一次使用next方法时的参数,只有从第二次使用next方法开始,参数才是有效的。从语义上讲,第一个next方法用来启动遍历器对象,所以不用带有参数。

1.Thunk函数

  • 编译器的"传名调用"实现,往往是将参数放到一个临时函数之中,再将这个临时函数传入函数体。这个临时函数就叫做 Thunk 函数。
function f(m){
  return m * 2;     
}

f(x + 5);
// 等同于

var thunk = function () {
  return x + 5;
};

function f(thunk){
  return thunk() * 2;
}

上面代码中,函数f的参数x + 5被一个函数替换了。凡是用到原参数的地方,对 Thunk 函数求值即可。
这就是 Thunk 函数的定义,它是"传名调用"的一种实现策略,用来替换某个表达式。

  • JS中的thunk函数
// 正常版本的readFile(多参数版本)
fs.readFile(fileName, callback);

// Thunk版本的readFile(单参数版本)
var readFileThunk = Thunk(fileName);
readFileThunk(callback);

var Thunk = function (fileName){
  return function (callback){
    return fs.readFile(fileName, callback); 
  

上面代码中,fs 模块的 readFile 方法是一个多参数函数,两个参数分别为文件名和回调函数。经过转换器处理,它变成了一个单参数函数,只接受回调函数作为参数。这个单参数版本,就叫做 Thunk 函数

thunk函数转换器:

var Thunk = function(fn){
  return function (){
    var args = Array.prototype.slice.call(arguments);
    return function (callback){
      args.push(callback);
      return fn.apply(this, args);
    }
  };
};

调用之后返回一个函数:

 let aaa = Thunk(xxxx);
     aaa = function() {
      var args = Array.prototype.slice.call(arguments);
      return function (callback){
        args.push(callback);
        return fn.apply(this, args);
      }
     }

此时调用 aaa 得,

    let bbb = aaa(x,y,z);
    //等同于
    bbb = function aaa(x,y,z) {
      var args = [x,y,z];
      return function (callback){
        args.push(callback);
        return xxx.apply(this, args);
      }
    } = function (callback) {
      var args = [x,y,z]
      args.push(callback);
      return xxx.apply(this, args)
    }

之后就很清楚了。thunk转换主要起到的作用将多参数函数替换成单参数的版本,且只接受该参数为回调函数.

  • 基于thunk函数的自动执行
 var fs = require('fs');
var thunkify = require('thunkify');
var readFile = thunkify(fs.readFile); // 把异步函数thunk化

var gen = function* (){
  var r1 = yield readFile('./index.html');
  console.log(r1.toString());
  var r2 = yield readFile('./normal.css');
  console.log(r2.toString());
};

// 手动执行的步骤
var g = gen();

var r1 = g.next()
r1.value(function(err, data){
  if (err) throw err;
  var r2 = g.next(data);
  console.log(r1.value.toString())
  r2.value(function(err, data){
    if (err) throw err;
    g.next(data);
  });
});

// 自动化,写一个递归函数
function run(fn) {
  var gen = fn;

  function next(err, data) {
    var result = gen.next(data);
    if (result.done) return;
    console.log(result.value.toString());
    result.value(next);
  }

  next();
}

run(gen());

2.基于Promise对象的自动执行

Promise对象对上面例子实现手动执行:

var fs = require('fs');

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

var gen = function* (){
  var f1 = yield readFile('/etc/fstab');
  var f2 = yield readFile('/etc/shells');
  console.log(f1.toString());
  console.log(f2.toString());
};

var g = gen();

g.next().value.then(function(data){
  g.next(data).value.then(function(data){
    g.next(data);
  });
})

由上式的手动执行,可知道Promise 自动执行就是一个递归,程序如下:

function run(gen){
  var g = gen();

  function next(data){
    var result = g.next(data);
    if (result.done) return result.value;
    result.value.then(function(data){
      next(data);
    });
  }

  next();
}

run(gen);

3. co基本思路

function co(gen) {
  var ctx = this;

  return new Promise(function(resolve, reject) {
    if (typeof gen === 'function') gen = gen.call(ctx);
    if (!gen || typeof gen.next !== 'function') return resolve(gen);
  });
}
  1. 接受generator函数,把它promise化。之后,先判断其是否为generator函数,是则执行函数,获取内部指针,以便通过next()执行,如果不是就返回,并将Promise对象的状态改为resolved
function co(gen) {
  var ctx = this;

  return new Promise(function(resolve, reject) {
    if (typeof gen === 'function') gen = gen.call(ctx);
    if (!gen || typeof gen.next !== 'function') return resolve(gen);

    onFulfilled();
    function onFulfilled(res) {
      var ret;
      try {
        ret = gen.next(res);
      } catch (e) {
        return reject(e);
      }
      next(ret);
    }    
  });
}
  1. next()执行包装成onFulfilled()函数,作用捕捉错误,将其reject()抛出。最后是next()的实现:
function next(ret) {
  if (ret.done) return resolve(ret.value);
  var value = toPromise.call(ctx, ret.value);
  if (value && isPromise(value)) return value.then(onFulfilled, onRejected);
  return onRejected(new TypeError('You may only yield a function, promise, generator, array, or object, '
        + 'but the following object was passed: "' + String(ret.value) + '"'));
    }
});

  1. 第一句,判断generator函数是否执行完毕,是则resolve,第二句,把执行next()之后得到的结果进行Promise化,第三句,对Promise化后的value,使用then方法,为返回值加上回调函数onFulfilledonRejected,然后通过onFulfilled函数再次调用next函数。此时上个value会把resolve的结果传到onFulfilled的参数res

4. async/await

  • 了解了以上的知识,就能知道,其实async/await就是一个将Generator 函数和自动执行器,包装在一起的函数。

简单的实现如下:

async function fn(args){
  // ...
}
// 等同于
function fn(args){ 
  return spawn(function*() {
    // ...
  }); 
}
// Generator函数自动执行器
function spawn(genF) {
  return new Promise(function(resolve, reject) {
    var gen = genF();
    function step(nextF) {
      try {
        var next = nextF();
      } catch(e) {
        return reject(e); 
      }
      if(next.done) {
        return resolve(next.value);
      } 
      Promise.resolve(next.value).then(function(v) {
        step(function() { return gen.next(v); });      
      }, function(e) {
        step(function() { return gen.throw(e); });
      });
    }
    step(function() { return gen.next(undefined); });
  });
}

参考

  1. http://www.ruanyifeng.com/blog/2015/05/co.html