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 相关文章推荐
Jquery 获得服务器控件值的方法小结
May 11 Javascript
jquery实现当滑动到一定位置时固定效果
Jun 17 Javascript
js实现iframe跨页面调用函数的方法
Dec 13 Javascript
jQuery使用$.ajax进行异步刷新的方法(附demo下载)
Dec 04 Javascript
AngularJS基础 ng-mousemove 指令简单示例
Aug 02 Javascript
AnjularJS中$scope和$rootScope的区别小结
Sep 18 Javascript
vue-cli项目如何使用vue-resource获取本地的json数据(模拟服务端返回数据)
Aug 04 Javascript
vue params、query传参使用详解
Sep 12 Javascript
利用Node.js了解与测量HTTP所花费的时间详解
Sep 22 Javascript
JQuery搜索框自动补全(模糊匹配)功能实现示例
Jan 08 jQuery
JavaScript中的事件与异常捕获详析
Feb 24 Javascript
vue 点击其他区域关闭自定义div操作
Jul 17 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脚本并取得参数的方法
2016/01/25 PHP
yii2中关于加密解密的那些事儿
2018/06/12 PHP
怎么用javascript进行拖拽
2006/07/20 Javascript
SWFObject 2.1以上版本语法介绍
2010/07/10 Javascript
js动态在form上插入enctype=multipart/form-data的问题
2012/05/24 Javascript
原生js 秒表实现代码
2012/07/24 Javascript
禁止你的左键复制实用技巧
2013/01/04 Javascript
jquery实现的带缩略图的焦点图片切换(自动播放/响应鼠标动作)
2013/01/23 Javascript
jQuery自定义事件的简单实现代码
2014/01/27 Javascript
javascript操作referer详细解析
2014/03/10 Javascript
一个网页标题title的闪动提示效果实现思路
2014/03/22 Javascript
jQuery中position()方法用法实例
2015/01/16 Javascript
js实现图片漂浮效果的方法
2015/03/02 Javascript
jQuery调用WebMethod(PageMethod) NET2.0的方法
2016/04/15 Javascript
轻松掌握JavaScript状态模式
2016/09/07 Javascript
微信小程序搭建(mpvue+mpvue-weui+fly.js)的详细步骤
2018/09/18 Javascript
vue 使某个组件不被 keep-alive 缓存的方法
2018/09/21 Javascript
vue axios 简单封装以及思考
2018/10/09 Javascript
JS关闭子窗口并且刷新上一个窗口的实现示例
2020/03/10 Javascript
ES6扩展运算符和rest运算符用法实例分析
2020/05/23 Javascript
微信小程序实现多选框功能的实例代码
2020/06/24 Javascript
vue实现循环滚动列表
2020/06/30 Javascript
python实现apahce网站日志分析示例
2014/04/02 Python
详解Python list 与 NumPy.ndarry 切片之间的对比
2017/07/24 Python
python3.4实现邮件发送功能
2018/05/28 Python
python实现将多个文件分配到多个文件夹的方法
2019/01/07 Python
python绘制规则网络图形实例
2019/12/09 Python
在notepad++中实现直接运行python代码
2019/12/18 Python
浅谈matplotlib中FigureCanvasXAgg的用法
2020/06/16 Python
Reebonz中国官网:新加坡奢侈品购物网站
2017/03/17 全球购物
煤矿机修工岗位职责
2014/02/07 职场文书
空气环保标语
2014/06/12 职场文书
幽默口才训练经典句子(48句)
2019/08/19 职场文书
Python3的进程和线程你了解吗
2022/03/16 Python
idea下配置tomcat避坑详解
2022/04/12 Servers
Python中生成随机数据安全性、多功能性、用途和速度方面进行比较
2022/04/14 Python