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

前言

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

对于await的底层机制这里就不详述了,以免将文章的篇幅拖的很长,需要的朋友们可以参考这篇文章:http://www.jb51.net/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除了用在发请求之外,还适用于其它异步场景,例如我在创建订单前先弹一个小框询问用户是要创建哪种类型的订单,然后再弹具体的设置订单的框,所以按正常思路这里需要传递一个按钮回调的点击函数,如下图所示:

但其实可以使用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呢?如下图所示:

生成器用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让代码少写了很多嵌套,很方便的逻辑处理,纵享丝滑。

总结

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

(0)

相关推荐

  • 详解ES6之async+await 同步/异步方案

    异步编程一直是JavaScript 编程的重大事项.关于异步方案, ES6 先是出现了 基于状态管理的 Promise,然后出现了 Generator 函数 + co 函数,紧接着又出现了 ES7 的 async + await 方案. 本文力求以最简明的方式来疏通 async + await. 异步编程的几个场景 先从一个常见问题开始:一个for 循环中,如何异步的打印迭代顺序? 我们很容易想到用闭包,或者 ES6 规定的 let 块级作用域来回答这个问题. for (let val of [

  • NodeJs通过async/await处理异步的方法

    场景 远古时代 我们在编写express后台,经常要有许多异步IO的处理.在远古时代,我们都是用chunk函数处理,也就是我们最熟悉的那种默认第一个参数是error的函数.我们来模拟一个Mongo数据库的操作,感受一下. mongoDb.open(function(err, db){ if(!err){ db.collection("users", function(err, collection){ if(!err){ let person = {name: "yika&q

  • async/await与promise(nodejs中的异步操作问题)

    举例写文章详情页面的时候的一个场景:首先更改文章详情中的 PV,然后读取文章详情,然后根据文章详情中文章 Id 查阅该文章评论和该文章作者信息.获取全部数据之后渲染文章详情页.数据库操作都是异步的,最直接想到的办法就是一层一层的回调函数,问题出来了:十分不雅观,要是层再多一点还会有更多麻烦.怎么解决?业内为了处理异步操作问题也是拼了,什么async,q,bluebird,co,处理方式不同,各有千秋,感兴趣可以了解一下,但是惊喜的发现nodejs 7.6已经默认支持ES7中的 async/awa

  • Js中async/await的执行顺序详解

    前言 虽然大家知道async/await,但是很多人对这个方法中内部怎么执行的还不是很了解,本文是我看了一遍技术博客理解 JavaScript 的 async/await(如果对async/await不熟悉可以先看下这篇文章)后拓展了一下,我理了一下await之后js的执行顺序,希望可以给别人解疑答惑,先简单介绍一下async/await. async/await 是一种编写异步代码的新方法.之前异步代码的方案是回调和 promise. async/await 是建立在 promise 的基础上

  • 深入理解ES7的async/await的用法

    在最开始学习ES6的Promise时,曾写过一篇博文 <promise和co搭配生成器函数方式解决js代码异步流程的比较> ,文章中对比了使用Promise和co模块搭配生成器函数解决js异步的异同. 在文章末尾,提到了ES7的async和await,只是当时只是简单的提了一下,并未做深入探讨. 在前两个月发布的Nodejs V7中,已添加了对async和await的支持,今天就来对这个东东做一下深入的探究.以更加优雅的方法写异步代码. async/await是什么 async/await可以

  • JavaScript中的await/async的作用和用法

    await/async 是 ES7 最重要特性之一,它是目前为止 JS 最佳的异步解决方案了.虽然没有在 ES2016 中录入,但很快就到来,目前已经在 ES-Next Stage 4 阶段. 直接上例子,比如我们需要按顺序获取:产品数据=>用户数据=>评论数据 老朋友 Ajax 传统的写法,无需解释 // 获取产品数据 ajax('products.json', (products) => { console.log('AJAX/products >>>', JSON

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

    前言 我们知道javascript是没办法阻塞的,所有的等待只能通过回调来完成,这就造成了回调嵌套的问题,导致代码乱到爆,这时候Await就有用处了. 对于await的底层机制这里就不详述了,以免将文章的篇幅拖的很长,需要的朋友们可以参考这篇文章:http://www.jb51.net/article/123257.htm,下面开始本文的正式内容. 利用Await减少回调嵌套 我们大家在开发的时候,有时候需要发很多请求,然后经常会面临嵌套回调的问题,即在一个回调里面又嵌了一个回调,导致代码层层缩

  • Linux中利用sudo进行赋权的方法详解

    前言 学习怎么在保护 root 密码的安全性的同时,为可信用户赋予所管理的网络功能和特定服务的权限. 我最近写了一个简短的 Bash 程序来将 MP3 文件从一台网络主机的 USB 盘中拷贝到另一台网络主机上去.拷贝出来的文件存放在一台志愿者组织所属服务器的特定目录下,在那里,这些文件可以被下载和播放. 我的程序还会做些其他事情,比如为了自动在网页上根据日期排序,在拷贝文件之前会先对这些文件重命名.在验证拷贝完成后,还会删掉 USB 盘中的所有文件.这个小程序还有一些其他选项,比如 -h 会显示

  • 利用JavaScript获取用户IP属地方法详解

    目录 写在前面 尝试一:navigator.geolocation 尝试二:sohu 的接口 尝试三:百度地图的接口 写在后面 写在前面 想要像一些平台那样显示用户的位置信息,例如某省市那样.那么这是如何做到的, 据说这个位置信息的准确性在通信网络运营商那里?先不管,先实践尝试下能不能获取. 尝试一:navigator.geolocation 尝试了使用 navigator.geolocation,但未能成功拿到信息. getGeolocation(){ if ('geolocation' in

  • JavaScript中数组去重常用的五种方法详解

    目录 1.对象属性(indexof) 2.new Set(数组) 3.new Map() 4.filter() + indexof 5.reduce() + includes 补充 原数组 const arr = [1, 1, '1', 17, true, true, false, false, 'true', 'a', {}, {}]; 1.对象属性(indexof) 利用对象属性key排除重复项 遍历数组,每次判断新数组中是否存在该属性,不存在就存储在新数组中 并把数组元素作为key,最后返

  • Android中gson、jsonobject解析JSON的方法详解

    JSON的定义: 一种轻量级的数据交换格式,具有良好的可读和便于快速编写的特性.业内主流技术为其提供了完整的解决方案(有点类似于正则表达式 ,获得了当今大部分语言的支持),从而可以在不同平台间进行数据交换.JSON采用兼容性很高的文本格式,同时也具备类似于C语言体系的行为. JSON对象: JSON中对象(Object)以"{"开始, 以"}"结束. 对象中的每一个item都是一个key-value对, 表现为"key:value"的形式, ke

  • 改变vue请求过来的数据中的某一项值的方法(详解)

    由于 JavaScript 的限制, Vue 不能检测以下变动的数组: 当你利用索引直接设置一个项时,例如:vm.items[indexOfItem] = newValue 当你修改数组的长度时,例如:vm.items.length = newLength 在 <template> <div> <ul> <li v-for = " (item,index) in list" v-text='`${item} - ${index} `'>&

  • 在Android中使用WebSocket实现消息通信的方法详解

    前言 消息推送功能可以说移动APP不可缺少的功能之一,一般简单的推送我们可以使用第三方推送的SDK,比如极光推送.信鸽推送等,但是对于消息聊天这种及时性有要求的或者三方推送不满足业务需求的,我们就需要使用WebSocket实现消息推送功能. 基本流程 WebSocket是什么,这里就不做介绍了,我们这里使用的开源框架是https://github.com/TakahikoKawasaki/nv-websocket-client 基于开源协议我们封装实现WebSocket的连接.注册.心跳.消息分

  • Node.js 中的 fs 模块与Path模块方法详解

    概述: 文件系统模块是一个简单包装的标准 POSIX 文件 I/O 操作方法集.可以通过调用 require("fs") 来获取该模块.文件系统模块中的所有方法均有异步和同步版本. 文件系统模块中的异步方法需要一个完成时的回调函数作为最后一个传入形参. 回调函数的构成由调用的异步方法所决定,通常情况下回调函数的第一个形参为返回的错误信息. 如果异步操作执行正确并返回,该错误形参则为null或者undefined.如果使用的是同步版本的操作方法,一旦出现错误,会以通常的抛出错误的形式返回

  • 利用OpenCV实现YOLO对象检测方法详解

    目录 前言 什么是YOLO物体检测器? 项目结构 检测图像 检测视频 前言 本文将教你如何使用YOLOV3对象检测器.OpenCV和Python实现对图像和视频流的检测.用到的文件有yolov3.weights.yolov3.cfg.coco.names,这三个文件的github链接如下: GitHub - pjreddie/darknet: Convolutional Neural Networks https://pjreddie.com/media/files/yolov3.weights

  • Java8利用Stream实现列表去重的方法详解

    目录 一. Stream 的distinct()方法 1.1 对于 String 列表的去重 1.2 对于实体类列表的去重 二. 根据 List<Object> 中 Object 某个属性去重 2.1 新建一个列表出来 2.2 通过 filter() 方法 一. Stream 的distinct()方法 distinct()是Java 8 中 Stream 提供的方法,返回的是由该流中不同元素组成的流.distinct()使用 hashCode() 和 eqauls() 方法来获取不同的元素.

随机推荐