浅析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获取GridView选择的行内容
Apr 14 Javascript
JavaScript 弹出窗体点击按钮返回选择数据的实现
Apr 01 Javascript
Jquery下:nth-child(an+b)的使用注意
May 28 Javascript
原生JS实现响应式瀑布流布局
Apr 02 Javascript
JavaScript中Function()函数的使用教程
Jun 04 Javascript
第一次接触神奇的Bootstrap菜单和导航
Aug 01 Javascript
JS中事件冒泡和事件捕获介绍
Dec 13 Javascript
vue实现点击图片放大效果
Aug 15 Javascript
在Vant的基础上封装下拉日期控件的代码示例
Dec 05 Javascript
浅入深出Vue之组件使用
Jul 11 Javascript
jquery.validate自定义验证用法实例分析【成功提示与择要提示】
Jun 06 jQuery
vue - props 声明数组和对象操作
Jul 30 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
mysql+php分页类(已测)
2008/03/31 PHP
PHP array操作10个小技巧分享
2011/06/23 PHP
PHP安全性漫谈
2012/06/28 PHP
thinkPHP5 tablib标签库自定义方法详解
2017/05/10 PHP
Laravel框架自定义公共函数的引入操作示例
2019/04/16 PHP
参考:关于Javascript中实现暂停的几篇文章
2007/03/04 Javascript
IE JS无提示关闭窗口不提示的方法
2010/04/29 Javascript
java与javascript之间json格式数据互转介绍
2013/10/29 Javascript
JavaScript给按钮绑定点击事件(onclick)的方法
2015/04/07 Javascript
Javascript中For In语句用法实例
2015/05/14 Javascript
百度地图API之本地搜索与范围搜索
2015/07/30 Javascript
js css实现垂直方向自适应的三角提示菜单
2016/06/26 Javascript
vuejs指令详解
2017/02/07 Javascript
浅谈$_FILES数组为空的原因
2017/02/16 Javascript
JS库之ParticlesJS使用简介
2017/09/12 Javascript
动手写一个angular版本的Message组件的方法
2017/12/16 Javascript
在一个页面实现两个zTree联动的方法
2017/12/20 Javascript
微信小程序实现的涂鸦功能示例【附源码下载】
2018/01/12 Javascript
详解如何在React组件“外”使用父组件的Props
2018/01/12 Javascript
使用angularjs.foreach时return的问题解决
2018/09/30 Javascript
JavaScript设计模式之责任链模式实例分析
2019/01/16 Javascript
js实现弹出框的拖拽效果实例代码详解
2019/04/16 Javascript
vue 监听 Treeselect 选择项的改变操作
2020/08/31 Javascript
Python文件及目录操作实例详解
2015/06/04 Python
Django中间件工作流程及写法实例代码
2018/02/06 Python
利用Python写一个爬妹子的爬虫
2018/06/08 Python
python3使用pandas获取股票数据的方法
2018/12/22 Python
Appium+Python自动化测试之运行App程序示例
2019/01/23 Python
aws 通过boto3 python脚本打pach的实现方法
2020/05/10 Python
W3C公布最新的HTML5标准草案
2008/10/17 HTML / CSS
小米旗下精品生活电商平台:小米有品
2018/12/18 全球购物
欧洲领先的火车票和大巴票预订平台:Trainline
2018/12/26 全球购物
2014年护理部工作总结
2014/11/14 职场文书
领导新年致辞2016
2015/07/29 职场文书
教你用python实现一个无界面的小型图书管理系统
2021/05/21 Python
Spring Cloud OAuth2实现自定义token返回格式
2022/06/25 Java/Android