理解javascript async的用法


Posted in Javascript onAugust 22, 2017

写在前面

本文将要实现一个顺序读取文件的最优方法,实现方式从最古老的回调方式到目前的async,也会与大家分享下本人对于thunk库与co库的理解。实现的效果:顺序读取出a.txt与b.txt,将读出的内容拼接成为一个字符串。

同步读取

const readTwoFile = () => {
  const f1 = fs.readFileSync('./a.txt'),
    f2 = fs.readFileSync('./b.txt');
  return Buffer.concat([f1, f2]).toString();
};

这种方式最利于我们理解,代码也很清楚,没有过多的嵌套,很好的维护,但是这种有着最大的问题,那就是性能,node所倡导的就是异步i/o来处理密集i/o,而同步的读取,很大的程度上浪费着服务器的cpu,这种方式的弊端明显的大于好处,所以直接pass掉。(其实node的任何异步编程的解决方案的目标都是要达到同步的语义,异步的执行。)

利用回调读取

const readTwoFile = () => {
  let str = null;
  fs.readFile('./a.txt', (err, data) => {
    if (err) throw new Error(err);
    str = data;
    fs.readFile('./b.txt', (err, data) => {
      if (err) throw new Error(err);
      str = Buffer.concat([str, data]).toString();
    });
  });
};

利用回调的方式,实现起来很简单,直接的嵌套下去就好,但是这种情况下很容易造成的就是不易维护,难以读懂的情况,最为极致的情况的就是回调地狱。

Promise实现

const readFile = file => 
  new Promise((reslove, reject) => {
    fs.readFile(file, (err, data) => {
      if (err) reject(err);
      reslove(data);
    });
  });
const readTwoFile = () => {
  let bf = null;
  readFile('./a.txt')
    .then(
      data => {
        bf = data;
        return readFile('./b.txt');
      }, 
      err => { throw new Error(err) }
    )
    .then(
      data => {
        console.log(Buffer.concat([bf, data]).toString())
      }, 
      err => { throw new Error(err) }
    );
};

Promise可以将横向增长的回调转化为纵向增长,能解决一些问题,但是Promise造成的问题就是代码冗余,一眼看过去,全部是then,也不是很爽,但是相比于回调函数嵌套来说,已经有了很大的提升。

yield

Generator很多语言中都有,本质上是协程,下面就来看一下协程,线程,进程的区别与联系:

  • 进程:操作系统中分配资源的基本单位
  • 线程:操作系统中调度资源的基本单位
  • 协程:比线程更小的的执行单元,自带cpu上下文,一个协程一个栈

一个进程中可能存在多个线程,一个线程中可能存在多个协程,进程、线程的切换由操作系统控制,而协程的切换由程序员自身控制。异步i/o利用回调的方式来应对i/o密集,同样的使用协程也可以来应对,协程的切换并没有很大的资源浪费,将一个i/o操作写成一个协程,这样进行i/o时可以吧cpu让给其他协程。

js同样支持协程,那就是yield。使用yield给我们直观的感受就是,执行到了这个地方停了下来,其他的代码继续跑,到你想让他继续执行了,他就是会继续执行。

function *readTwoFile() {
  const f1 = yield readFile('./a.txt');
  const f2 = yield readFile('./b.txt'); 
  return Buffer.concat([f1, f2]).toString();
}

yield下的顺序读取呈现的也是一种顺序读取的方式,对于readFile来看有两种不同的实现方式,

利用thunkify

const thunkify = (fn, ctx) => (...items) => (done) => {
  ctx = ctx || null;
  let called = false;
  items.push((...args) => {
    if (called) return void 0;
    called = true;
    done.apply(ctx, args);
  });
  try {
    fn.apply(ctx, items);  
  } catch(err) {
    done(err);
  }
};

thunkify函数就是一种柯里化得思想,最后的传入参数done就为回调函数,利用thunkify可以很轻松的实现yield函数的自动化流程:

const run = fn => {
  const gen = fn();
  let res;
  (function next(err, data) {
    let g = gen.next(data);
    if (g.done) return void 0;
    g.value(next);
  })();
};

利用Promise

const readFile = file => 
  new Promise((reslove, reject) => {
    fs.readFile(file, (err, data) => {
      if (err) reject(err);
      reslove(data);
    });
  });
const run = fn => {
  const gen = fn();
  let str = null;
  (function next(err, data) {
    let res = gen.next(data);
    if (res.done) return void 0;
    res.value.then(
      data => {
        next(null, data);
      }, 
      err => { throw new Error(err); }
    );
  })();
};
run(readTwoFile);

上面两种方式都可以达到自动执行yield的过程,那么有没有一种方式,可以兼容这两种实现方式呢,tj大神又给出了一个库,那就是co库,先来看下用法:

// readTwoFile的实现与上面类似,readFile既可以利用Promise也可以利用thunkify
// co库返回一个Promise对象
co(readTwoFile).then(data => console.log(data));

来看下co库的实现,co库默认会返回一个Promise对象,对于yield之后的值(如上面的res.value),co库会将其转换为一个Promise。实现思想很简单,基本还是利用递归的方式,大体的思路如下:

const baseHandle = handle => res => {
  let ret;
  try {
    ret = gen[handle](res);
  } catch(e) {
    reject(e);
  }
  next(ret);
};
function co(gen) {
  const ctx = this,
    args = Array.prototype.slice.call(arguments, 1);
  return new Promise((reslove, reject) => {
    if (typeof gen === 'function') gen = gen.apply(ctx, args);
    if (!gen || typeof gen.next !== 'function') return resolve(gen);

    const onFulfilled = baseHandle('next'),
      onRejected = baseHandle('throw');

    onFulfilled();

    function next(ret) {
      if (ret.done) reslove(ret.value);
      // 将yield的返回值转换为Proimse
      const value = toPromise.call(ctx, ret.value);
      if (value && isPromise(value)) return value.then(onFulfilled, onRejected);
      return onRejected(new TypeError('yield type error'));
    }
  });
}

toPromise就是将一些类型转换为Promise,从这里我们可以看出的是可以将哪些类型放在yield后面,这里就来看一个常用的:

// 把thunkify之后的函数转化为Promise的形式
function thunkToPromise(fn) {
  const ctx = this;
  return new Promise(function (resolve, reject) {
    fn.call(ctx, function (err, res) {
      if (err) return reject(err);
      if (arguments.length > 2) res = slice.call(arguments, 1);
      resolve(res);
    });
  });
}

最近Node已经支持了async/await,可以利用其来做异步操作:

终极解决

const readFile = file => 
  new Promise((reslove, reject) => {
    fs.readFile(file, (err, data) => {
      if (err) reject(err);
      reslove(data);
    });
  });
const readTwoFile = async function() {
  const f1 = await readFile('./a.txt');
  const f2 = await readFile('./b.txt');  
  return Buffer.concat([f1, f2]).toString();
};
readTwoFile().then(data => {
  console.log(data);
});

async/await做的就是将Promise对象给串联起来,避免了then的调用方式,代码非常的易读,就是一种同步的方式。不再需要借助其他外界类库(比如co库)就可以优雅的解决回调的问题。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
Jquery each方法跳出循环,并获取返回值(实例讲解)
Dec 12 Javascript
JS阻止冒泡事件以及默认事件发生的简单方法
Jan 17 Javascript
如何实现textarea里的不同文本显示不同颜色
Jan 20 Javascript
基于jquery插件实现拖拽删除图片功能
Aug 27 Javascript
深入理解React中es6创建组件this的方法
Aug 29 Javascript
DOM 事件的深入浅出(一)
Dec 05 Javascript
jQuery中页面返回顶部的方法总结
Dec 30 Javascript
深入研究jQuery图片懒加载 lazyload.js使用方法
Aug 16 jQuery
vuejs router history 配置到iis的方法
Sep 20 Javascript
element-ui 实现响应式导航栏的示例代码
May 08 Javascript
快速了解Vue父子组件传值以及父调子方法、子调父方法
Jul 15 Javascript
vue项目打包后请求地址错误/打包后跨域操作
Nov 04 Javascript
React Native之TextInput组件解析示例
Aug 22 #Javascript
EasyUI的DataGrid每行数据添加操作按钮的实现代码
Aug 22 #Javascript
浅谈箭头函数写法在ReactJs中的使用
Aug 22 #Javascript
Extjs 中的 Treepanel 实现菜单级联选中效果及实例代码
Aug 22 #Javascript
bootstrap3-dialog-master模态框使用详解
Aug 22 #Javascript
关于JS与jQuery中的文档加载问题
Aug 22 #jQuery
bootstrap confirmation按钮提示组件使用详解
Aug 22 #Javascript
You might like
一个简洁的多级别论坛
2006/10/09 PHP
PHP中一些可以替代正则表达式函数的字符串操作函数
2014/11/17 PHP
PHP Socket网络操作类定义与用法示例
2017/08/30 PHP
利用Homestead快速运行一个Laravel项目的方法详解
2017/11/14 PHP
Javascript前端UI框架Kit使用指南之kitjs事件管理
2014/11/28 Javascript
jQuery中parents()方法用法实例
2015/01/07 Javascript
浅谈javascript事件取消和阻止冒泡
2015/05/26 Javascript
谈谈impress.js初步理解
2015/09/09 Javascript
jQuery Validation PlugIn的使用方法详解
2015/12/18 Javascript
js实现移动端微信页面禁止字体放大
2017/02/16 Javascript
Webpack常见静态资源处理-模块加载器(Loaders)+ExtractTextPlugin插件
2017/06/29 Javascript
Three.js实现简单3D房间布局
2018/12/30 Javascript
vue v-for直接循环数字实例
2019/11/07 Javascript
[05:03]2018DOTA2亚洲邀请赛主赛事首日回顾
2018/04/04 DOTA
Python 序列化 pickle/cPickle模块使用介绍
2014/11/30 Python
Python StringIO模块实现在内存缓冲区中读写数据
2015/04/08 Python
Python随机数random模块使用指南
2016/09/09 Python
python实现字典(dict)和字符串(string)的相互转换方法
2017/03/01 Python
python实现屏保计时器的示例代码
2018/08/08 Python
Python如何转换字符串大小写
2020/06/04 Python
Python实现自动装机功能案例分析
2020/10/22 Python
python 下载文件的几种方法汇总
2021/01/06 Python
HTML5中通过li-canvas轻松实现单图、多图、圆角图绘制,单行文字、多行文字等
2018/11/30 HTML / CSS
canvas环形倒计时组件的示例代码
2018/06/14 HTML / CSS
LookFantastic丹麦:英国美容护肤精品在线商城
2016/08/18 全球购物
日本整理专家Marie Kondo的官方在线商店:KonMari
2020/06/29 全球购物
如何将一个描述日期或日期/时间的字符串转换为一个Date对象
2015/10/13 面试题
建筑学专业自荐书
2014/07/09 职场文书
机械机修工岗位职责
2014/08/03 职场文书
邓小平理论心得体会
2014/09/09 职场文书
女方离婚起诉书
2015/05/18 职场文书
盗窃案辩护词
2015/05/21 职场文书
医院病假条范文
2015/08/17 职场文书
python单元测试之pytest的使用
2021/06/07 Python
win server2012 r2服务器共享文件夹如何设置
2022/06/21 Servers
从原生JavaScript到React深入理解
2022/07/23 Javascript