代码改变世界

ES6生成器函数generator

2017-08-13 22:38  龙恩0707  阅读(1087)  评论(0编辑  收藏  举报

ES6生成器函数generator

    generator是ES6新增的一个特殊函数,通过 function* 声明,函数体内通过 yield 来指明函数的暂停点,该函数返回一个迭代器,并且函数执行到 yield语句前面暂停,之后通过调用返回的迭代器next()方法来执行yield语句。
如下代码演示:

function* generator() {
  yield 1;
  yield 2;
  yield 3;
}
var gen = generator();

如上代码:generator函数的调用方法与普通函数一样,也是在函数名后面加上一对圆括号。不同的是,调用generator函数后,该函数并不执行,返回的也不是函数运行的结果,而是一个指向内部状态的指针对象,我们可以通过调用next方法,使指针移向下一个状态。

console.log(gen.next());  // {value:1, done: false}
console.log(gen.next());  // {value:2, done: false}
console.log(gen.next());  // {value:3, done: false}
console.log(gen.next());  // {value: undefined, done: true}

如上代码,返回的迭代器每次调用next会返回一个对象 {value: "xxx", done: true/false}, value是返回值,done表示的是否达到迭代器的结尾,true是代表已经到了结尾,false代表没有。

我们可以通过 返回的对象的done是否为true来判断生成器是否执行结束,通过返回对象的value可以得到yield的返回值。如果返回对象的done值为true的话,那么该值value就为undefined。
迭代器next()方法还可以传入一个参数,这个参数会作为上一个yield语句的返回值,如果不传参数,yield语句中生成器函数内的返回值是 undefined。

如下代码演示:

function* test(x) {
  let y = yield x;
  console.log(1111)
  yield y;
}
var t = test(3);
console.log(t.next()); // {value:3, done: false}
console.log(t.next()); // {value: undefined, done: false}
console.log(t.next()); // {value: undefined, done: true}

下面我们向第二个next方法传入值为5, 因此y的值被赋值为5,如下代码:

function* test(x) {
  let y = yield x;
  yield y;
}
var t = test(3);
console.log(t.next()); // {value:3, done: false}
console.log(t.next(5)); // {value: 5, done: false}

我们也可以通过 for...of循环可以自动遍历 generator函数生成对象,此时不需要调用next方法。如下代码:

function* foo() {
  yield 1;
  yield 2;
  yield 3;
  yield 4;
  yield 4+1;
  yield 6;
}
for (let v of foo()) {
  console.log(v); // 1, 2, 3, 4, 5, 6
}

1-2 异常处理 

下面是一个简单的列子来捕获generator里的异常

function* error() {
  try {
    throw new Error('error');
    yield 11;
  } catch(err) {
    console.log(err);
  }
}
var it = error();
it.next();

1-3 Generators的异步的应用

    Generators 最主要的特点是单线程执行,同步风格代码编写,同时又允许将 代码异步的特性隐藏在程序的实现细节中。通过generators函数,我们将程序具体的实现细节从异步代码中抽离出来,可以很好的实现了功能和关注点的分离。
下面是使用ajax的demo,异步ajax请求,当请求完成后的数据,需要将返回的数据值传递给下一个请求去,也就是说下一个请求要依赖于上一个请求返回的数据依次传递下去代码如下:

function requestURL(url, cb) {
  // ajax 请求, 请求完成后 调用cb函数
  $.ajax({
    url: url,
    type: 'get',
    dataType: 'json',
    success: function(d) {
      cb(d);
    }
  })
}
var url = 'http://httpbin.org/get';
requestURL(url, function(res) {
  console.log(res);
  var url2 = 'http://httpbin.org/get?origin='+res.origin;
  requestURL(url2, function(res2) {
    console.log(res2);
  });
});

如上代码,我们看到异步ajax请求依次嵌套回调。
下面我们可以使用 generator去重新实现一下,如下代码:

function requestURL(url, cb) {
  // ajax 请求, 请求完成后 调用cb函数
  $.ajax({
    url: url,
    type: 'get',
    dataType: 'json',
    success: function(d) {
      cb(d);
    }
  })
}
var url = 'http://httpbin.org/get';
function request(url) {
  requestURL(url, function(res) {
    it.next(res);
  })
}
function* main() {
  var res1 = yield request(url);
  console.log(res1);
  var res2 = yield request(url + '?origin='+res1.origin);
  console.log(res2);
}

var it = main();
it.next(); 

如上代码是使用Generator实现的异步代码请求;实现思路如下:
首先 requestURL 函数,发一个ajax请求,请求成功后调用回调函数 cb;request函数是封装requestURL函数,请求成功后回调,调用it.next()方法,
将指针指向下一个yield,main函数是请求启动代码,当 var it = main()时候,会调用main函数,但是只是会调用,但是并不会执行代码,因此需要 it.next()方法执行第一个 yield request(url) 方法,因此会调用request方法,再继续调用requestURL方法,最后会输出res1, 然后 requestURL方法的回调,又调用 
it.next()方法,因此会执行main里面yield request的第二句代码,代码执行和第一次一样,最后输出res2.

上面的代码有一个地方需要理解的是,ajax请求成功以后 会继续调用 it.next()方法,那么成功后的数据如何传递到 res1呢?我们看到没有return语句返回的?
这是因为当 在ajax的callback调用的时候,它首先会传递ajax返回的结果。返回值发送到我们的generator时候,var res1 = yield request(url);给暂停了,然后在调用it.next()方法,会执行下一个请求。
1-4 Generator+Promise 实现异步请求

上面的generator代码可以做一些简单的异步请求,但是会受到一些限制,比如如下:
    1. 没有明确的方法来处理请求error;ajax请求有时候会超时或失败的情况下,这个时候我们需要使用generator中的it.throw(),同时还需要使用try catch来处理错误的逻辑。
   2. 一般情况下我们请求不会涉及到并行请求,但是如果有的需求是并行的话,比如说同时发2个或者多个ajax请求的话,由于 generator yidle机制都是逐步暂停的,无法同时运行另一个或多个任务,
因此,generator不太容易操作多个任务。

我们现在使用promise修改下 request的方法,让yield返回一个promise,所有代码如下:

function requestURL(url, cb) {
  // ajax 请求, 请求完成后 调用cb函数
  $.ajax({
    url: url,
    type: 'get',
    dataType: 'json',
    success: function(d) {
      cb(d);
    }
  })
}
var url = 'http://httpbin.org/get';
function request(url) {
  return new Promise((resolve, reject) => {
    requestURL(url, resolve);
  });
}
function runGenerator(cb) {
  var it = cb(),
    ret;
  // 异步遍历generator
  (function iterator(val){
    console.log(111)
    console.log(val);  // undefined
    // 返回一个promise
    ret = it.next(val); 
    console.log(ret);  // {value: Promise, done: false}
    if (!ret.done) {
      if('then' in ret.value) {
        // 等待接受promise
        ret.value.then(iterator);
      } else {
        setTimeout(function() {
          iterator(ret.value);
        }, 0)
      }
    }
  })();
}
runGenerator(function *main() {
  var res1 = yield request(url);
  console.log(res1);
  var res2 = yield request(url + '?origin='+res1.origin);
  console.log(res2);
});

上面代码 :

function request(url) {
  return new Promise((resolve, reject) => {
    requestURL(url, resolve);
  });
}

当ajax请求完成后,会返回这个promise,同时接收 yield request(url); 然后通过next()方法恢复generator运行,并把他们传递下去,第一次运行iterator方法后,val值为undefined,然后调用 ret = it.next(val); 返回一个promise ,继续打印下 console.log(ret); 返回如下: {value: Promise, done: false}
虽然上面代码运行正常,但是我们还未处理代码异常的情况下,因此下面处理下代码异常的情况下:

function requestURL(url, cb) {
  // ajax 请求, 请求完成后 调用cb函数
  $.ajax({
    url: url,
    type: 'get',
    dataType: 'json',
    success: function(d) {
      cb(null, d);
    },
    error: function(err) {
      cb(err, text);
    }
  })
}
var url = 'http://httpbin.org/get';
function request(url) {
  return new Promise((resolve, reject) => {
    requestURL(url, function(err, text){
      if (err) {
        reject(err);
      } else {
        resolve(text);
      }
    });
  });
}
function runGenerator(cb) {
  var it = cb(),
    ret;
  // 异步遍历generator
  (function iterator(val){
    console.log(111)
    console.log(val);  // undefined
    // 返回一个promise
    ret = it.next(val); 
    console.log(ret);  // {value: Promise, done: false}
    if (!ret.done) {
      if('then' in ret.value) {
        // 等待接受promise
        ret.value.then(iterator);
      } else {
        setTimeout(function() {
          iterator(ret.value);
        }, 0)
      }
    }
  })();
}

runGenerator(function *main() {
  try {
    var res1 = yield request(url);
  } catch(err) {
    console.log(err);
    return;
  }
  console.log(res1);
  try {
    var res2 = yield request(url + '?origin='+res1.origin);
  } catch(err) {
    console.log(err);
    return;
  }
  console.log(res2);
})

第一步处理异常的情况下 代码如上已经完成了,现在来解决第二步 使用Promise + generator 解决多个请求同时运行,需要使用Promise.all()方法来解决。
如下代码:

function requestURL(url, cb) {
  // ajax 请求, 请求完成后 调用cb函数
  $.ajax({
    url: url,
    type: 'get',
    dataType: 'json',
    success: function(d) {
      cb(null, d);
    },
    error: function(err) {
      cb(err, text);
    }
  })
}
var url = 'http://httpbin.org/get';
function request(url) {
  return new Promise((resolve, reject) => {
    requestURL(url, function(err, text){
      if (err) {
        reject(err);
      } else {
        resolve(text);
      }
    });
  })
  // 获取返回值后
  .then(function(d) {
    console.log('dddddd');
    console.log(d);
    return d
  });
}
function runGenerator(cb) {
  var it = cb(),
    ret;
  // 异步遍历generator
  (function iterator(val){
    // 返回一个promise
    ret = it.next(val); 
    if (!ret.done) {
      if('then' in ret.value) {
        // 等待接受promise
        ret.value.then(iterator);
      } else {
        setTimeout(function() {
          iterator(ret.value);
        }, 0)
      }
    }
  })();
}

runGenerator(function *main() {
  var res1 = yield Promise.all([
    request(url),
    request(url)
  ]);
  console.log(2222)
  console.log(res1);
  var result = yield request(url + '?origin='+res1[0].origin);
  console.log(result);
})