ES7中利用Await减少回调嵌套的方法详解


Posted in Javascript onNovember 01, 2017

前言

我们知道javascript是没办法阻塞的,所有的等待只能通过回调来完成,这就造成了回调嵌套的问题,导致代码乱到爆,这时候Await就有用处了。

对于await的底层机制这里就不详述了,以免将文章的篇幅拖的很长,需要的朋友们可以参考这篇文章:https://3water.com/article/123257.htm,下面开始本文的正式内容。

利用Await减少回调嵌套

我们大家在开发的时候,有时候需要发很多请求,然后经常会面临嵌套回调的问题,即在一个回调里面又嵌了一个回调,导致代码层层缩进得很厉害。

如下代码所示:

ajax({
 url: "/list",
 type: "GET",
 success: function(data) {
 appendToDOM(data);
 ajax({
 url: "/update",
 type: "POST",
 success: function(data) {
 util.toast("Success!");
 })
 });
 }
});

这样的代码看起来有点吃力,这种异步回调通常可以用Promise优化一下,可以把上面代码改成:

new Promise(resolve => {
 ajax({
 url: "/list",
 type: "GET",
 success: data => resolve(data);
 })
}).then(data => {
 appendToDOM(data);
 ajax({
 url: "/update",
 type: "POST",
 success: function(data) {
 util.toast("Successfully!");
 }) 
 }); 
});

Promise提供了一个resolve,方便通知什么时候异步结束了,不过本质还是一样的,还是使用回调,只是这个回调放在了then里面。

当需要获取多次异步数据的时候,可以使用Promise.all解决:

let orderPromise = new Promise(resolve => {
 ajax("/order", "GET", data => resolve(data));
});
let userPromise = new Promise(resolve => {
 ajax("/user", "GET", data => resolve(data));
});

Promise.all([orderPromise, userPromise]).then(values => {
 let order = values[0],
 user = values[1];
});

但是这里也是使用了回调,有没有比较优雅的解决方式呢?

ES7的await/async可以让异步回调的写法跟写同步代码一样。第一个嵌套回调的例子可以用await改成下面的代码:

// 使用await获取异步数据
let leadList = await new Promise(resolve => {
 ajax({
 url: "/list",
 type: "GET",
 success: data => resolve(data);
 });
});

// await让代码很自然地像瀑布流一样写下来 
appendToDom(leadList);
ajax({
 url: "/update",
 type: "POST",
 success: () => util.toast("Successfully");
});

Await让代码可以像瀑布流一样很自然地写下来。

第二个例子:获取多次异步数据,可以改成这样:

let order = await new Promise(
 resolve => ajax("/order", data => resovle(data))),

 user = await new Promise(
 resolve => ajax("/user", data => resolve(data)));

// do sth. with order/user

这种写法就好像从本地获取数据一样,就不用套回调函数了。

Await除了用在发请求之外,还适用于其它异步场景,例如我在创建订单前先弹一个小框询问用户是要创建哪种类型的订单,然后再弹具体的设置订单的框,所以按正常思路这里需要传递一个按钮回调的点击函数,如下图所示:

ES7中利用Await减少回调嵌套的方法详解

但其实可以使用await解决,如下代码所示:

let quoteHandler = require("./quote");
// 弹出框询问用户并得到用户的选择
let createType = await quoteHandler.confirmCreate();

quote里面返回一个Promise,监听点击事件,并传递createType:

let quoteHandler = {
 confirmCreate: function(){
 dialog.showDialog({
 contentTpl: tpl,
 className: "confirm-create-quote"
 });
 let $quoteDialog = $(".confirm-create-quote form")[0];
 return new Promise(resolve => {
 $(form.submit).on("click", function(event){
 resolve(form.createType.value);
 });
 });
 }

}

这样外部调用者就可以使用await,而不用传递一个点击事件的回调函数了。

但是需要注意的是await的一次性执行特点。相对于回调函数来说,await的执行是一次性的,例如监听点击事件,然后使用await,那么点击事件只会执行一次,因为代码从上往下执行完了,所以当希望点击之后出错了还能继续修改和提交就不能使用await,另外使用await获取异步数据,如果出错了,那么成功的resolve就不会执行,后续的代码也不会执行,所以请求出错的时候基本逻辑不会有问题。

要在babel里面使用await,需要:

(1)安装一个Node包

npm install --save-dev babel-plugin-transform-async-to-generator

(2)在工程的根目录添加一个.babelrc文件,内容为:

{
 "plugins": ["transform-async-to-generator"]
}

(3)使用的时候先引入一个模块

require("babel-polyfill");

然后就可以愉快地使用ES7的await了。

使用await的函数前面需要加上async关键字,如下代码:

async showOrderDialog() {
 // 获取创建类型
 let createType = await quoteHandler.confirmCreate();

 // 获取老订单数据 
 let orderInfo = await orderHandler.getOrderData();
}

我们再举一个例子:使用await实现JS版的sleep函数,因为原生是没有提供线程休眠函数的,如下代码所示:

function sleep (time) {
 return new Promise(resolve => 
 setTimeout(() => resolve(), time));
}

async function start () {
 await sleep(1000);
}

start();

babel的await实现是转成了ES6的generator,如下关键代码:

while (1) {
 switch (_context.prev = _context.next) {
 case 0:
 _context.next = 2;
 // sleep返回一个Promise对象
 return sleep(1000);

 case 2:
 case "end": 
 return _context.stop();
 }
}

而babel的generator也是要用ES5实现的,什么是generator呢?如下图所示:

ES7中利用Await减少回调嵌套的方法详解

生成器用function*定义,每次执行生成器的next函数的时候会返回当前生成器里用yield返回的值,然后生成器的迭代器往后走一步,直到所有yield完了。

有兴趣的可以继续研究babel是如何把ES7转成ES5的,据说原生的实现还是直接基于Promise.

使用await还有一个好处,可以直接try-catch捕获异步过程抛出的异常,因为我们是不能直接捕获异步回调里面的异常的,如下代码:

let quoteHandler = {
 confirmCreate: function(){
 $(form.submit).on("click", function(event){
 // 这里会抛undefined异常:访问了undefined的value属性
 callback(form.notFoundInput.value);
 });
 }
}

try {
 // 这里无法捕获到异常
 quoteHandler.confirmCreate();
} catch (e) {

}

上面的try-catch是没有办法捕获到异常的,因为try里的代码已经执行完了,在它执行的过程中并没有异常,因此无法在这里捕获,如果使用Promise的话一般是使用Promise链的catch:

let quoteHandler = {
 confirmCreate: function(){
 return new Promise(resolve => {
 $(form.submit).on("click", function(event){
 // 这里会抛undefined异常:访问了undefined的value属性
 resolve(form.notFoundInput.value);
 });
 });
 }
}

quoteHandler.confirmCreate().then(createType => {

}).catch(e => {
 // 这里能捕获异常
});

而使用await,我们可以直接用同步的catch,就好像它真的变成同步执行了:

try {
 createType = await quoteHandler.confirmCreate("order");
}catch(e){
 console.log(e);
 return;
}

总之使用await让代码少写了很多嵌套,很方便的逻辑处理,纵享丝滑。

总结

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对三水点靠木的支持。

Javascript 相关文章推荐
理清apply(),call()的区别和关系
Aug 14 Javascript
提高javascript效率 一次判断,而不要次次判断
Mar 30 Javascript
javascript实现复选框选中属性
Mar 25 Javascript
文件上传的几个示例分享【推荐】
Dec 16 Javascript
JavaScript正则表达式exec/g实现多次循环用法示例
Jan 17 Javascript
详解Javascript中DOM的范围
Feb 13 Javascript
vue2.0 自定义日期时间过滤器
Jun 07 Javascript
js实现页面多个日期时间倒计时效果
Jun 20 Javascript
一篇文章弄懂javascript中的执行栈与执行上下文
Aug 09 Javascript
浅谈layui使用模板引擎动态渲染元素要注意的问题
Sep 14 Javascript
vue项目实现图片上传功能
Dec 23 Javascript
JavaScript正则表达式验证登录实例
Mar 18 Javascript
JavaScript实现带有子菜单和控件的slider轮播图效果
Nov 01 #Javascript
bootstrap Table的一些小操作
Nov 01 #Javascript
react-native fetch的具体使用方法
Nov 01 #Javascript
Vue异步加载about组件
Oct 31 #Javascript
微信小程序顶部可滚动导航效果
Oct 31 #Javascript
React Native使用Modal自定义分享界面的示例代码
Oct 31 #Javascript
Bootstrap3.3.7导航栏下拉菜单鼠标滑过展开效果
Oct 31 #Javascript
You might like
PHP编程实现csv文件导入mysql数据库的方法
2017/04/29 PHP
PHP编程计算两个时间段是否有交集的实现方法(不算边界重叠)
2017/05/30 PHP
ThinkPHP实现转换数据库查询结果数据到对应类型的方法
2017/11/16 PHP
php将从数据库中获得的数据转换成json格式并输出的方法
2018/08/21 PHP
Egret引擎开发指南之运行项目
2014/09/03 Javascript
JS+CSS实现的漂亮渐变背景特效代码(6个渐变效果)
2016/03/25 Javascript
每日十条JavaScript经验技巧(二)
2016/06/23 Javascript
jQuery基于ajax方式实现用户名存在性检查功能示例
2017/02/10 Javascript
BootStrap中jQuery插件Carousel实现轮播广告效果
2017/03/27 jQuery
浅谈redux以及react-redux简单实现
2018/08/28 Javascript
详解vue2.0 资源文件assets和static的区别
2018/11/27 Javascript
微信小程序中使用Async-await方法异步请求变为同步请求方法
2019/03/28 Javascript
vue下canvas裁剪图片实例讲解
2020/04/16 Javascript
详解React中共享组件逻辑的三种方式
2021/02/02 Javascript
[01:37]DOTA2超级联赛专访ChuaN 传奇般的电竞之路
2013/06/19 DOTA
[51:00]Secret vs VGJ.S 2018国际邀请赛淘汰赛BO3 第一场 8.24
2018/08/25 DOTA
Python实现分割文件及合并文件的方法
2015/07/10 Python
浅析Python的Django框架中的Memcached
2015/07/23 Python
Python多进程同步简单实现代码
2016/04/27 Python
Python操作SQLite数据库的方法详解【导入,创建,游标,增删改查等】
2017/07/11 Python
python+matplotlib绘制饼图散点图实例代码
2018/01/20 Python
Pycharm导入Python包,模块的图文教程
2018/06/13 Python
Pycharm之快速定位到某行快捷键的方法
2019/01/20 Python
Python使用import导入本地脚本及导入模块的技巧总结
2019/08/07 Python
python接口调用已训练好的caffe模型测试分类方法
2019/08/26 Python
Pycharm创建项目时如何自动添加头部信息
2019/11/14 Python
Python numpy数组转置与轴变换
2019/11/15 Python
Pytorch使用MNIST数据集实现CGAN和生成指定的数字方式
2020/01/10 Python
Python 代码调试技巧示例代码
2020/08/11 Python
selenium如何定位span元素的实现
2021/01/13 Python
DVF官方网站:美国时装界尊尚品牌
2017/08/29 全球购物
Ruby如何创建一个线程
2013/03/10 面试题
幼儿园大班开学寄语
2014/08/02 职场文书
死亡证明书样本说明
2014/10/18 职场文书
2014年残联工作总结
2014/11/21 职场文书
vue点击弹窗自动触发点击事件的解决办法(模拟场景)
2021/05/25 Vue.js