浅析JavaScript异步代码优化


Posted in Javascript onMarch 18, 2019

前言

在实际编码中,我们经常会遇到Javascript代码异步执行的场景,比如ajax的调用、定时器的使用等,在这样的场景下也经常会出现这样那样匪夷所思的bug或者糟糕的代码片段,那么处理好你的Javascript异步代码成为了异步编程至关重要的前提。下面我们从问题出发,一步步完善你的异步代码。

异步问题

1. 回调地狱

首先,我们来看下异步编程中最常见的一种问题,便是回调地狱。它的出现是由于异步代码执行时间的不确定性及代码间的依赖关系引发的,比如:

// 一个动画结束后,执行下一个动画,下一个动画结束后再执行下一个动画
$('#box').animate({width: '100px'}, 1000, function(){
  $('#box').animate({height: '100px'}, 1000, function(){
    $('#box').animate({left: 100}, 1000);
  });
});

由于我们不知道第一个动画什么时候开始或者什么时候结束,所以我们把第二个动画的执行内容放到了第一个动画的结束事件里,把第三个动画放到了第二个动画的结束事件里,这时候如果有很多这样的动画,那么就会形成回调地狱。

2. 捕获异常

除了回调地狱,如果我们需要在异步代码中捕获异常也比较麻烦,可能需要手动配置捕获方法:

try {
  throw new Error('fail');
} catch (e) {
  console.log(e);
}

这样的代码书写明显不是我们想要的,不仅不利于维护,而且也在一定程度上违背了良好的Javascript编码规范。

解决方案

那么我们如何优雅的写好我们的异步代码呢?我主要列了以下5种常见方案:

1. callback

callback顾名思义便是回调,但并不是将回调内容放在异步方法里,而是放到外部的回调函数中,比如问题1的代码我们通过callback可以变成这样:

$('#box').animate({width: '100px'}, 1000, autoHeight);

function autoHeight() {
  $('#box').animate({height: '100px'}, 1000, autoLeft);
}

function autoLeft() {
  $('#box').animate({left: 100}, 1000);
}

如此我们看似异步的代码变成了同步的写法,避免了层层嵌套的写法,看上去也流畅了很多。同时使用callback也是异步编程最基础和核心的一种解决思路。

2. Promise

基于callback,Promise目前也被广泛运用,其是异步编程的一种解决方案,比传统的回调函数解决方案更合理和强大。相信了解ES6的同学肯定不会陌生。

比如我们现在有这样一个场景,我们需要异步加载一张图片,在图片加载成功后做一些操作,这里我不想用回调函数或者将逻辑写在图片的成功事件里,那么用Promise我们可以这样写:

let p = new Promise((resolve, reject) => {
  let img = new Image(); // 创建图片对象

  // 图片加载成功事件
  img.onload = function() {
    resolve(img); // 输出图片对象
  };

  // 图片加载失败事件
  img.onerror = function() {
    reject(new Error('load error')); // 输出错误
  };

  img.src = 'xxx'; // 图片路径
});

// Promise then回调
p
.then(result => {
  $('#box').append(result); // 成功后我们把图片放到页面上
})
.catch(error => {
  console.log(error); // 打印错误
})

通过Promise我们把图片构建加载的逻辑和成功或失败后的处理逻辑拆分了开来,将回调函数的嵌套,改成链式调用,同时使用Promise的catch事件回调后异常捕获也变得十分方便。

当然如果要等待多个异步请求完成执行某些操作,可以使用Promise.all方法,如:

let p = Promise.all([p1, p2, p3]); // 其中p1、p2、p3都是Promise实例

p.then(result => console.log(result));

当然Promise也有其相应的缺点,比如下一个then回调只能获取上一个then返回的数据,不能跨层获取,同时大量的then回调也会使代码不容易维护。

3. Generator

与Promise一样,Generator 函数也是 ES6 提供的一种异步编程解决方案,其会返回一个遍历器对象,异步任务需要暂停的地方我们可以使用yield语句,比如:

function* getData() {
  let result = yield fetch("xxx"); // 调用ajax,yield命令后面只能是 Thunk 函数或 Promise 对象

  console.log(result);
}

// 执行
let g = getData();
let result = g.next(); // { value: [object Promise], done: false }

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

Generator中遇到yield的地方会进行暂停,所以我们需要手动调用next方法往下,next返回值的 value 属性便是我们需要的数据,这里是fetch方法返回的Promise对象,所以我们需要使用then回调,最后再调用g.next(data)结束并输出数据。

Generator 函数的缺点在于,我们每一次执行yield语句都需要手动进行next,不是很方便。

4. co

为了解决上方Generator函数需手动执行next方法的问题,TJ Holowaychuk大神编写了一个co函数库,能够使Generator 函数可以自动执行,比如原来我们需要这样:

let files = function* (){
  var f1 = yield readFile('/xxx/xxx'); // 读取file1文件
  var f2 = yield readFile('/xxx/xxx'); // 读取file2文件

  console.log(f1.toString());
  console.log(f2.toString());
};

files.next(); // 执行yield
files.next(); // 执行yield

使用co后:

var co = require('co');

co(files);
 

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

co(files).then(() => {
 console.log('执行完成');
});

最后我们可以看到我们没有手动执行next方法,也会打印出所读取的文件。

co模块虽然很好的帮助了我们解决了Generator函数必须靠执行器的问题,但是使用起来我们都需要额外引入一个模块,那么有没有更加方便的方式来解决呢?继续往下看。

5. async and await

除了以上4中方式可以解决Javascript异步编程的问题外,ES7还提供了更加方便的async 函数和await命令,了解一下?

其实async是 Generator 函数的语法糖,不同点在于其内置了执行器,也就是说async函数自带执行器。看一下下面的例子:

let p1 = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve(1);
  }, 1000);
});

let p2 = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve(2); 
  }, 1000);
});

async function waitFn() {
  let a = await p1; // await命令后面可以是 Promise 对象和原始类型的值,如果使原始类型最终也会返回为Promise对象
  let b = await p2;

  return a + b
}

// async函数的返回值是 Promise 对象, 可以用then方法指定下一步的操作
waitFn().then(result => {
  console.log(result); // 2s后输出3
});

async函数内部return语句返回的值,会成为then方法回调函数的参数。因此这就像极了利用co包裹起来的Generator函数,只是把*替换成了async,把yield替换成了await。

可以说async and await 是ES7中最重要的一个特性,虽然其也存在一些弊端,但是相比较而言用其处理异步代码来说还是比较得心应手的。

结语

本文简单介绍了处理好Javascript异步代码的五种常见方式,每一种方式都有其使用和存在的条件和必要性,有兴趣的同学可以对其进行单独的拓展和探究,只有了解并掌握每一种方式各自的优点并加以运用,才能享受异步编程带来的快感。

以上所述是小编给大家介绍的JavaScript异步代码优化详解整合,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对三水点靠木网站的支持!

Javascript 相关文章推荐
用javascript自动显示最后更新时间
Mar 15 Javascript
jquery解析xml字符串示例分享
Mar 25 Javascript
控制台报错object is not a function的解决方法
Aug 24 Javascript
整理AngularJS框架使用过程当中的一些性能优化要点
Mar 05 Javascript
使用Web Uploader实现多文件上传
Jun 08 Javascript
Vue项目中引入外部文件的方法(css、js、less)
Jul 24 Javascript
react native带索引的城市列表组件的实例代码
Aug 08 Javascript
javaScript产生随机数的用法小结
Apr 21 Javascript
微信小程序实现图片上传放大预览删除代码
Jun 28 Javascript
js中Generator函数的深入讲解
Apr 07 Javascript
VUE脚手架具体使用方法
May 20 Javascript
JS如何实现基于websocket的多端桥接平台
May 14 Javascript
js实现图片局部放大效果详解
Mar 18 #Javascript
详解在React项目中安装并使用Less(用法总结)
Mar 18 #Javascript
vue动画效果实现方法示例
Mar 18 #Javascript
node.js实现微信开发之获取用户授权
Mar 18 #Javascript
学习node.js 断言的使用详解
Mar 18 #Javascript
React 使用Hooks简化受控组件的状态绑定
Mar 18 #Javascript
JavaScript显式数据类型转换详解
Mar 18 #Javascript
You might like
人工智能开始玩《星际争霸2》 你的操作跟得上吗?
2017/08/11 星际争霸
php递归列出所有文件和目录的代码
2008/09/10 PHP
整理的9个实用的PHP库简介和下载
2010/11/09 PHP
zf框架的db类select查询器join链表使用示例(zend框架)
2014/03/14 PHP
PHP使用Redis替代文件存储Session的方法
2017/02/15 PHP
如何运行/调试你的PHP代码
2020/10/23 PHP
javascript处理table表格的代码
2010/12/06 Javascript
jquery+css+ul模拟列表菜单具体实现思路
2013/04/15 Javascript
Bootstrap框架动态生成Web页面文章内目录的方法
2016/05/12 Javascript
jQuery实现点击某个div打开层,点击其他div关闭层实例分析(阻止冒泡)
2016/11/18 Javascript
jQuery实现base64前台加密解密功能详解
2017/08/29 jQuery
基于Axios 常用的请求方法别名(详解)
2018/03/13 Javascript
解决Layui 表单提交数据为空的问题
2018/08/15 Javascript
利用Vue构造器创建Form组件的通用解决方法
2018/12/03 Javascript
微信小程序网络层封装的实现(promise, 登录锁)
2019/05/08 Javascript
vue中使用v-model完成组件间的通信
2019/08/22 Javascript
基于iview-admin实现动态路由的示例代码
2019/10/02 Javascript
[54:51]Ti4 冒泡赛第二轮LGD vs C9 3
2014/07/14 DOTA
解决安装tensorflow遇到无法卸载numpy 1.8.0rc1的问题
2018/06/13 Python
Python面向对象之类和实例用法分析
2019/06/08 Python
在django-xadmin中APScheduler的启动初始化实例
2019/11/15 Python
python读取图片颜色值并生成excel像素画的方法实例
2021/02/19 Python
css3实例教程 一款纯css3实现的环形导航菜单
2014/10/20 HTML / CSS
瑞典领先的汽车零部件网上零售商:bildelaronline24.se
2017/01/12 全球购物
英国网络托管和域名领导者:Web Hosting UK
2017/10/15 全球购物
Amcal中文官网:澳洲综合性连锁药房
2019/03/28 全球购物
军人违纪检讨书
2014/02/04 职场文书
小学教师培训方案
2014/06/09 职场文书
报考公务员诚信承诺书
2014/08/29 职场文书
关于运动会的广播稿(10篇)
2014/09/12 职场文书
单位实习工作证明怎么写
2014/11/02 职场文书
优秀班集体申报材料
2014/12/25 职场文书
小兵张嘎观后感300字
2015/06/03 职场文书
小学科学课教学反思
2016/02/23 职场文书
vue2实现provide inject传递响应式
2021/05/21 Vue.js
Springboot如何使用logback实现多环境配置?
2021/06/16 Java/Android