generator、async函数的异步编程应用研究

简介:Generator函数主要应用于函数的异步操作,在ES6诞生之前,异步编程的方法,大概有下面四种: “ 回调函数 、事件监听 、发布/订阅 、Promise对象 ”。但是Generator函数的出现使异步编程更加的方便快捷。

 

一、Generator函数应用研究

1. 测试小体验

function* gen(x){
  try {
    var y = yield x + 2;
  } catch (e){
    console.log(e);
  }
  return y;
}

var g = gen(1);
g.next(); // {value: 3, done: false}
g.throw('出错了');  // 出错了 {value: undefined, done: true}


2. JS中的Thunk函数

简述:Thunk 函数是自动执行 Generator 函数的一种方法。编译器的“传名调用”实现,往往是将参数放到一个临时函数之中,再将这个临时函数传入函数体。这个临时函数就叫做 Thunk 函数。

参考案例:

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

f(x + 5);

// 等同于
var thunk = function () {
  return x + 5;
};

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

核心:javascript使传值调用的,并非传名调用,在javascript中,Thunk函数替换的而不是表达式,而是多参函数,将其替换成了一个只接受回调函数的作为参数的单参数函数。

测试案例分析:

// 正常版本的readFile(多参数版本)
fs.readFile(fileName, callback);

// Thunk版本的readFile(单参数版本)
var Thunk = function (fileName) {
  return function (callback) {
    return fs.readFile(fileName, callback);
  };
};

var readFileThunk = Thunk(fileName);
readFileThunk(callback);

* 且任何函数只要有回调参数,都可以写成Thunk函数形式,如下是一个简单的Thunk函数转换器

测试案例分析:

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

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

3. 使用Thunk封装Generator函数

(1)测试案例:

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

var gen = function* (){
  var r1 = yield readFileThunk('/etc/fstab');
  console.log(r1.toString());
  var r2 = yield readFileThunk('/etc/shells');
  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);
  r2.value(function (err, data) {
    if (err) throw err;
    g.next(data);
  });
});

上面代码中,变量g是 Generator 函数的内部指针,表示目前执行到哪一步。next方法负责将指针移动到下一步,并返回该步的信息(value属性和done属性)。

仔细查看上面的代码,可以发现 Generator 函数的执行过程,其实是将同一个回调函数,反复传入next方法的value属性。这使得我们可以用递归来自动完成这个过程。

(2)Thunk 自动执行 Generator 函数

测试案例:

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

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

  next();
}

function* g() {
  // ...
}

run(g);

4. 使用co模块自动执行Generator函数

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

var co = require('co');
co(gen);

且co函数返回一个Promise对象,因此可以用then方法添加回调函数:

co(gen).then(function (){
  console.log('Generator 函数执行完成');
});

5. 使用Promise自动执行Generator函数

测试案例分析:

// 业务逻辑代码
var fs = require('fs');

var readFile = function (fileName){
  return new Promise(function (resolve, reject){
    fs.readFile(fileName, function(error, data){
      if (error) return 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());
};


// 自动执行Generator函数封装
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);

6. Genarator函数使用示例

可以参考文章:https://blog.csdn.net/ganyingxie123456/article/details/78152770

二、async函数

为什么和Generator函数一起研究呢?因为async核心代码就是Generator实现的,可以说成async函数就是Generator得语法糖。

* async函数相对于Generator函数改进的地方:

1.自带执行器,类似于co + generator的结合
2.co模块约定,yield命令后面只能是 Thunk 函数或 Promise 对象,而async函数的await命令后面,可以是 Promise 对象和原始类型的值;
3.返回值是 Promise;async函数内部return语句返回的值,会成为then方法回调函数的参数

测试案例:指定多少毫秒后输出一个值

function timeout(ms) {
  return new Promise((resolve) => {
    setTimeout(resolve, ms);
  });
}

async function asyncPrint(value, ms) {
  await timeout(ms);
  console.log(value);
}

asyncPrint('hello world', 50);  // 指定 50 毫秒以后,输出hello world

* 使用注意点如下:

1.await命令后面的Promise对象,运行结果可能是rejected,所以最好把await命令放在try...catch代码块中
2.多个await命令后面的异步操作,如果不存在继发关系,最好让它们同时触发,如下:

// 写法一
let [foo, bar] = await Promise.all([getFoo(), getBar()]);

// 写法二
let fooPromise = getFoo();
let barPromise = getBar();
let foo = await fooPromise;
let bar = await barPromise;

3.await命令只能用在async函数之中,如果用在普通函数,就会报错
4.async 函数可以保留运行堆栈

详细文档地址:http://es6.ruanyifeng.com/#docs/async

猜你喜欢

转载自blog.csdn.net/WU5229485/article/details/84953880
今日推荐