ES6中的异步

首先这篇文章是参考阮一峰老师的ES6章节中的generator部分写的,再加上我自己的理解,新手如果要学习ES6推荐去看阮老师的 ES6

异步

所谓的异步就是假如说现在有俩个过程,第一个过程为我正在做饭,第二个过程为在我做饭的期间有一个人来敲门,这种情形对于异步来说就是,我首先需要停止做饭,然后去开门,当人进来之后,我把门关上了,然后继续做饭去了,简单的说就是我停止一个过程,去做另一个过程,然后另一个过程结束后再来继续做原来没有做完的过程

异步处理的几种方式


回调函数的异步


我们以读取文件为例

fs.readFile(fileA, 'utf-8', function(err, data){
    fs.readFile(fiileB, 'utf-8', function(err,data)}{)
})

readFile()函数是nodejs中的,首先读取fileA,然后读取fileA结束后调用回调函数然后在读取fileB,上面如果读取多个文件会看起来特别冗余,那么ES6中Promise就起到了作用

Promise函数的异步处理


还是以上述读取文件的例子

var readFile = require('fs-readfile-promise');
readFile(fileA)
.then(function(data) {
  console.log(data);
}).then(function(){
  return readFile(fileB);
}).then(function(data){
  console.log(data);
}).catch(function(err){
  console.log(err);
})

这个的意思就是引用fs-readfile-promise模板,使用bash的可以用npm install --save-dev fs-readfile-promise来安装,这个模块返回一个promise版本的readFile()所以它可以调用then方法,然后在打印出fileA中的数据,then返回一个promise然后在调用then方法,然后返回promise版本的readFile,返回具有readFile的promise,然后在调用then输出fileB的数据

generator实现异步


var fetch = require('node-fech');
function *gen(){
  var url = 'haha';
  var result = yield fetch(url);
console.log(url);
}
var g = new gen();
var result = g.next();
result.value.then(function(data){
  return data.json();
}).then(function(data){
  g.next(data);
})

首先先创建了一个generator函数,然后fetch函数返回的是一个promise对象,所以首先第一次调用next(),返会的是一个对象,这个对象的value属性就是fetch返回的promise对象,然后调用then函数将数据转为json,然后then函数返回一个promise对象,然后在调用then对象,然后把上一个then返回的数据传递给第二个then里面的function函数

thunk函数

什么是thunk函数

简单的说就是如果你想往一个函数中传递一个x+5,参数,现在就把这个x+5用一个函数表示,如下

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

这个用来表示x+5的函数就是thunk
js中的thunk函数

fs.readFile(fileName, callback);
var thunk = function(fileName) {
 return function(callback){
  return fs.readFile(filename, callback);
}
}
var read = thunk(fileName)(callback);

简单的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);
}
}
}

分析上面Array.prototype.slice.call(arguments);这一行代码,就是slice函数是在原型对象中,然后使slice被arguments调用,因为slice中没有其他参数,所以这行代码直接返回一个数组,这个数组其实就是arguments中各参数组成的

thunkify模块

安装该模块使用 npm install thunkify

var thunkify = require('thunkify');
var fs = require('fs');
read('package.json')(function(err, str){})

下面我们来分析一下thunkify的源码

function thunkify(fn){
  return function(){
//第一个返回函数就是把fn的参数传递给args这个数组
   var args = new Array(arguments.length);
//ctx就是上下文环境
   var ctx = this;
   for(var i = 0; i < args.length; ++i){
    args[i] = arguments[i];
   }
//第二个返回的函数,这里的done相当于回调函数这里保证回调函数只
//被执行一次
   return function(done){
  var called;
    args.push(function(){
     if(called) return;
     called = true;
     done.apply(null, arguments);
    })
//执行fn函数然后,这里边的args包括fn函数以及保证回调函数执行一次
    try{
      fn.apply(ctx, args);
}catch(err){
      done(err);
   }
   }
  }
}
function f(a, c, callback){
  var sum = a + b;
  callback(sum);
  callback(sum);  
}
var ft = thunkify(f); //第一次调用thunkify函数就是返回第一个函数
var print = console.log.bind(console);
ft(1, 2)(print); //ft(1,2),这时候把1,2加入args,然后这里面的print就相于
//done,也就是callback,所以callback只会被调用一次

自动执行generator函数


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());
};
//下面是怎样实现generator函数自动执行
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);
});
});

从以上可以看出主要是通过next函数实现generator函数执行的,往next中传入的值,也就是yield返回的值,也就是gen函数中的r1和r2

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();
}
var g = function* (){
 var f1 = yield readFileThunk('fileA');
 var f2 = yield readFileThunk('fileB');
 // ...
  var fn = yield readFileThunk('fileN');
};
run(g);

这里面的在yield后面的函数就是thunk函数,上面的next函数就是thunk函数的回调函数,也就是执行的函数,所以上面的generator能够一直自动执行

CO模板实现generator函数自动执行


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

其实你会发现这个co模块和上面的run函数的功能一样,所以这个co模块和run函数的实现都差不多

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());
};

上面吧readFile函数包装成一个promise对象,以实现在动执行

CO源码分析


function co(gen) {
var ctx = this;
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)
  + '"'
  )
  );
 }
 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);
 }
 });
 }

上面代码中,next函数的内部代码,一共只有四行命令。

第一行,检查当前是否为 Generator 函数的最后一步,如果是就返回。

第二行,确保每一步的返回值,是 Promise 对象。

第三行,使用then方法,为返回值加上回调函数,然后通过onFulfilled函数再次调用next函数。

第四行,在参数不符合要求的情况下(参数非 Thunk 函数和 Promise 对象),将 Promise 对象的状态改为rejected,从而终止执行。
有时间继续分析这个源码今天太累了

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,547评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,399评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,428评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,599评论 1 274
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,612评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,577评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,941评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,603评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,852评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,605评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,693评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,375评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,955评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,936评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,172评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,970评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,414评论 2 342

推荐阅读更多精彩内容