浅析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 对表格的行和列都能加亮显示
Dec 26 Javascript
firefox和IE系列的相关区别整理 以备后用
Dec 28 Javascript
JS 按钮点击触发(兼容IE、火狐)
Aug 07 Javascript
javascript的alert box在java中如何显示多行
May 18 Javascript
js控制再次点击按钮之间的间隔时间可防止重复提交
Aug 01 Javascript
jQuery 实现自动填充邮箱功能(带下拉提示)
Oct 14 Javascript
谈一谈JS消息机制和事件机制的理解
Apr 14 Javascript
快速使用Bootstrap搭建传送带
May 06 Javascript
微信小程序实现点击返回顶层的方法
Jul 12 Javascript
原生js实现each方法实例代码详解
May 27 Javascript
layui原生表单验证的实例
Sep 09 Javascript
Canvas跟随鼠标炫彩小球的实现
Apr 11 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
php 分页原理详解
2009/08/21 PHP
php 8小时时间差的解决方法小结
2009/12/22 PHP
PHP得到mssql的存储过程的输出参数功能实现
2012/11/23 PHP
Mac OS下配置PHP+MySql环境
2015/02/25 PHP
PHP 微信扫码支付源代码(推荐)
2016/11/03 PHP
textContent在Firefox下与innerText等效的属性
2007/05/12 Javascript
JavaScript Object的extend是一个常用的功能
2009/12/02 Javascript
js下用gb2312编码解码实现方法
2009/12/31 Javascript
extJs 下拉框联动实现代码
2010/04/09 Javascript
Ext修改GridPanel数据和字体颜色、css属性等
2014/06/13 Javascript
input标签内容改变的触发事件介绍
2014/06/18 Javascript
JavaScript动态生成二维码图片
2016/04/20 Javascript
30分钟快速掌握Bootstrap框架
2016/05/24 Javascript
Bootstrap作品展示站点实战项目2
2016/10/14 Javascript
JSON键值对序列化和反序列化解析
2017/01/24 Javascript
基于jQuery实现弹幕APP
2017/02/10 Javascript
详解如何用webpack打包一个网站应用项目
2017/07/12 Javascript
详解vue2.0 不同屏幕适配及px与rem转换问题
2018/02/23 Javascript
JS实现图片旋转动画效果封装与使用示例
2018/07/09 Javascript
JS实现进度条动态加载特效
2020/03/25 Javascript
vue实现移动端H5数字键盘组件使用详解
2020/08/25 Javascript
基于element-ui对话框el-dialog初始化的校验问题解决
2020/09/11 Javascript
深入浅析python定时杀进程
2016/06/06 Python
selenium + python 获取table数据的示例讲解
2018/10/13 Python
Pycharm保存不能自动同步到远程服务器的解决方法
2019/06/27 Python
详解Python在使用JSON时需要注意的编码问题
2019/12/06 Python
django框架forms组件用法实例详解
2019/12/10 Python
PyTorch中Tensor的数据类型和运算的使用
2020/09/03 Python
Python中用xlwt制作表格实例讲解
2020/11/05 Python
CSS3中box-shadow的用法介绍
2015/07/15 HTML / CSS
YOOX美国官方网站:全球著名的多品牌时尚网络概念店
2016/09/11 全球购物
英国第一的购买便宜玩具和游戏的在线购物网站:Bargain Max
2018/01/24 全球购物
Java里面如何创建一个内部类的实例
2015/01/19 面试题
检察院起诉意见书
2015/05/20 职场文书
党支部评议意见
2015/06/02 职场文书
Redis做数据持久化的解决方案及底层原理
2021/07/15 Redis