ES6—Generator函数的异步应用

传统方法

  • 回调函数
  • 事件监听
  • 发布/订阅
  • Promise对象

基本概念

异步

  1. 不连续的执行,即某一任务先执行一段,待响应后继续执行另一端。
  2. 同步是连续执行,不能插入其他任务,因此执行操作的时间程序只能等待。

回调函数

把任务的第二段写在一个函数里,等到重新执行这个任务时直接调用这个函数。

Promise

为解决多重嵌套的写法,允许将回调函数的嵌套改写成链式调用。

Generator函数

协程

异步编程(多任务)的解决方案中其中一种,即多个线程互相协作,完成异步任务。

  1. 协程A开始执行
  2. A暂停,B获得执行权
  3. B交还执行权
  4. A恢复执行
function *asyncJob() {
	// some your code
	var f = yield readFile(fileA);
	// some your other code
}

协程的Generator函数实现

特点:交出函数的执行权(暂停执行),需要暂停的地方用yield语句
能封装异步操作的原因:

  1. 可以暂停执行和恢复执行(根本原因
  2. 数据交换
  3. 错误处理机制

Generator函数的数据交换和错误处理

  1. 数据交换
function* gen(x) {
	var y = yield x + 2;
	return y;
}

var g = gen(1);
g.next()  // { value: 3, done: false}
g.next(2)  // { value: 2, done: true}
  1. 错误处理
function* gen(x) {
	try {
		var y = yield x + 2;
	} catch (e) {
		console.log(e);
	}
	return y;
}

var g = gen(1);
g.next();
g.throw('出错了');  //出错了

异步任务的封装

Generator函数执行异步任务

var fetch = require('node-fetch');

function* gen() {
	var url = 'url';
	var result = yield fetch(url);
	console.log(result.bio);
}

执行操作

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

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

Thunk函数

自动执行Generator函数的一种方法

参数的求值策略

var x = 1;
function f(m) {
	return m * 2;
}

f(x + 5)
  1. 传值调用
    在进入函数体之前就计算x+5,传入6
    比较简单,但对参数求值时,实际上还没有用到这个参数,可能造成性能损失。
  2. 传名调用
    直接将x+5传入函数体,用到的时候求值

Thunk函数的含义

传名调用的实现是将参数放到一个临时函数中,将临时函数传入函数体内,临时函数就是Thunk函数

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

f(x + 5);

等同于

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

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

Js的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);

Thunkify模块

生产环境中的转换器建议使用Thunkify模块

  1. 安装
$ npm install thunkify
  1. 使用方式
var thunkify = require('thunkify');
var fs = require('fs');

var read = thunkify(fs.readFile);
read('package.json')(function(err, str){
	// your code
});

Generator函数的流程管理

以读取文件为例

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

// 使用Thunk函数将执行权再交还给Generator函数
var gen = function* () {
	var r1 = yield readFileThunk('/etc/fstab');
	console.log(r1.toString());
	var r2 = yield.readFileThunk('/etc/shells');
	console.log(r2.toString());
};

Thunk函数的自动化流程管理

基于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);

co模块

基本用法

用于Generator函数自动执行,co函数返回一个Promise对象,可以用then方法添加回调函数

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

原理

Generator是一个异步操作的容器,它的自动执行需要可以自动交回执行权的机制。

  1. 回调函数。将异步操作包装成Thunk函数,在回调函数里面交回执行权
  2. Promise对象。将异步操作包装成Promise对象,用then方法交回执行权
    co模块将两种自动执行器(Thunk函数和Promise对象)包装成一个模块,因此yield命令后面只能是Thunk函数或Promise对象。

基于Promise对象的自动执行

以读取文件为例

  1. 将fs模块的readFile方法包装成一个Promise对象
var fs = require('fs');

var readFile = function (fileName) {
	return new Promise(function (resolve, reject) {
		fs.readFile(fileName, function(err, data) {
			if (err) return reject(err);
			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());
};
  1. 手动执行上面的Generator函数
var g = gen();
g.next().value.then(function(data) {
	g.next(data).value.then(function(data) {
		g.next(data);
	});
});
  1. 自动的执行器
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);

co模块的源码

function co(gen) {
  var ctx = this;

  // 接受Generator函数作为参数,返回一个Promise对象
  return new Promise(function(resolve, reject) {
  // 检查是否为Generator函数,是就执行,得到一个内部指针对象。不是就返回
    if (typeof gen === 'function') gen = gen.call(ctx);
    if (!gen || typeof gen.next !== 'function') return resolve(gen);

	// 将next方法包装成onFilfilled函数,主要是为了能够捕捉抛出的错误
    onFulfilled();
    function onFulfilled(res) {
      var ret;
      try {
        ret = gen.next(res);
      } catch (e) {
        return reject(e);
      }
      next(ret);
    }
  });
}

// next函数
function next(ret) {
  if (ret.done) return resolve(ret.value);  // 检查是否为最后一步
  var value = toPromise.call(ctx, ret.value);  // 确保每一步的返回值是Promise对象
  if (value && isPromise(value)) return value.then(onFulfilled, onRejected);  // 使用then方法为返回值加上回调函数,后通过onFulfilled函数再次调用next函数
  // 参数不符合要求时将Promise对象状态改为rejected,从而终止
  return onRejected(
    new TypeError(
      'You may only yield a function, promise, generator, array, or object, '
      + 'but the following object was passed: "'
      + String(ret.value)
      + '"'
    )
  );
}

处理并发的异步操作

co支持并发的异步操作,即允许某些操作同时进行,等到全部完成才进行下一步。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值