Async/Await替代Promise的6个理由


Posted in Javascript onJune 15, 2019

前言

Node.js 7.6 已经支持 async/await 了,如果你还没有试过,这篇博客将告诉你为什么要用它。

Async/Await 简介

对于从未听说过 async/await 的朋友,下面是简介:

  • async/await 是写异步代码的新方式,以前的方法有回调函数和Promise。
  • async/await 是基于 Promise 实现的,它不能用于普通的回调函数。
  • async/await 与 Promise 一样,是非阻塞的。
  • async/await 使得异步代码看起来像同步代码,这正是它的魔力所在。

Async/Await 语法

示例中,getJSON 函数返回一个 promise,这个 promise 成功 resolve 时会返回一个 json 对象。我们只是调用这个函数,打印返回的 JSON 对象,然后返回”done”。

使用 Promise 是这样的:

const makeRequest = () =>
getJSON().then(data => {
console.log(data);
return "done";
});
makeRequest();

使用 Async/Await 是这样的:

const makeRequest = async () => {
console.log(await getJSON());
return "done";
};
makeRequest();

它们有一些细微不同:

函数前面多了一个 async 关键字。await 关键字只能用在 async 定义的函数内。async 函数会隐式地返回一个 promise,该 promise 的 reosolve 值就是函数 return 的值。(示例中 reosolve 值就是字符串”done”)

第 1 点暗示我们不能在最外层代码中使用 await,因为不在 async 函数内。

// 不能在最外层代码中使用await
await makeRequest();
// 这是会出事情的
makeRequest().then(result => {
// 代码
});

await getJSON()表示 console.log 会等到 getJSON 的 promise 成功 reosolve 之后再执行。

为什么 Async/Await 更好?

1. 简洁

由示例可知,使用 Async/Await 明显节约了不少代码。我们不需要写.then,不需要写匿名函数处理 Promise 的 resolve 值,也不需要定义多余的 data 变量,还避免了嵌套代码。这些小的优点会迅速累计起来,这在之后的代码示例中会更加明显。

2. 错误处理

Async/Await 让 try/catch 可以同时处理同步和异步错误。在下面的 promise 示例中,try/catch 不能处理 JSON.parse 的错误,因为它在 Promise 中。我们需要使用.catch,这样错误处理代码非常冗余。并且,在我们的实际生产代码会更加复杂。

const makeRequest = () => {
try {
getJSON().then(result => {
// JSON.parse可能会出错
const data = JSON.parse(result);
console.log(data);
});
// 取消注释,处理异步代码的错误
// .catch((err) => {
// console.log(err)
// })
} catch (err) {
console.log(err);
}
};

使用 async/await 的话,catch 能处理 JSON.parse 错误:

const makeRequest = async () => {
try {
// this parse may fail
const data = JSON.parse(await getJSON());
console.log(data);
} catch (err) {
console.log(err);
}
};

3. 条件语句

下面示例中,需要获取数据,然后根据返回数据决定是直接返回,还是继续获取更多的数据。

const makeRequest = () => {
return getJSON().then(data => {
if (data.needsAnotherRequest) {
return makeAnotherRequest(data).then(moreData => {
console.log(moreData);
return moreData;
});
} else {
console.log(data);
return data;
}
});
};

这些代码看着就头痛。嵌套(6 层),括号,return 语句很容易让人感到迷茫,而它们只是需要将最终结果传递到最外层的 Promise。

上面的代码使用 async/await 编写可以大大地提高可读性:

const makeRequest = async () => {
const data = await getJSON();
if (data.needsAnotherRequest) {
const moreData = await makeAnotherRequest(data);
console.log(moreData);
return moreData;
} else {
console.log(data);
return data;
}
};

4. 中间值

你很可能遇到过这样的场景,调用 promise1,使用 promise1 返回的结果去调用 promise2,然后使用两者的结果去调用 promise3。你的代码很可能是这样的:

const makeRequest = () => {
return promise1().then(value1 => {
return promise2(value1).then(value2 => {
return promise3(value1, value2);
});
});
};

如果 promise3 不需要 value1,可以很简单地将 promise 嵌套铺平。如果你忍受不了嵌套,你可以将 value 1 & 2 放进 Promise.all 来避免深层嵌套:

const makeRequest = () => {
return promise1()
.then(value1 => {
return Promise.all([value1, promise2(value1)]);
})
.then(([value1, value2]) => {
return promise3(value1, value2);
});
};

这种方法为了可读性牺牲了语义。除了避免嵌套,并没有其他理由将 value1 和 value2 放在一个数组中。

使用 async/await 的话,代码会变得异常简单和直观。

const makeRequest = async () => {
const value1 = await promise1();
const value2 = await promise2(value1);
return promise3(value1, value2);
};

5. 错误栈

下面示例中调用了多个 Promise,假设 Promise 链中某个地方抛出了一个错误:

const makeRequest = () => {
return callAPromise()
.then(() => callAPromise())
.then(() => callAPromise())
.then(() => callAPromise())
.then(() => callAPromise())
.then(() => {
throw new Error("oops");
});
};
makeRequest().catch(err => {
console.log(err);
// output
// Error: oops at callAPromise.then.then.then.then.then (index.js:8:13)
});

Promise 链中返回的错误栈没有给出错误发生位置的线索。更糟糕的是,它会误导我们;错误栈中唯一的函数名为 callAPromise,然而它和错误没有关系。(文件名和行号还是有用的)。

然而,async/await 中的错误栈会指向错误所在的函数:

const makeRequest = async () => {
await callAPromise();
await callAPromise();
await callAPromise();
await callAPromise();
await callAPromise();
throw new Error("oops");
};
makeRequest().catch(err => {
console.log(err);
// output
// Error: oops at makeRequest (index.js:7:9)
});

在开发环境中,这一点优势并不大。但是,当你分析生产环境的错误日志时,它将非常有用。这时,知道错误发生在 makeRequest 比知道错误发生在 then 链中要好。

6. 调试

最后一点,也是非常重要的一点在于,async/await 能够使得代码调试更简单。2 个理由使得调试 Promise 变得非常痛苦:

不能在返回表达式的箭头函数中设置断点

如果你在.then 代码块中设置断点,使用 Step Over 快捷键,调试器不会跳到下一个.then,因为它只会跳过异步代码。
使用 await/async 时,你不再需要那么多箭头函数,这样你就可以像调试同步代码一样跳过 await 语句。

结论

Async/Await 是近年来 JavaScript 添加的最革命性的特性之一。它会让你发现 Promise 的语法有多糟糕,而且提供了一个直观的替代方法。

忧虑

对于 Async/Await,也许你有一些合理的怀疑:

它使得异步代码不再明显: 我们已经习惯了用回调函数或者.then 来识别异步代码,我们可能需要花数个星期去习惯新的标志。但是,C#拥有这个特性已经很多年了,熟悉它的朋友应该知道暂时的稍微不方便是值得的。
Node 7 不是 LTS(长期支持版本): 但是,Node 8 下个月就会发布,将代码迁移到新版本会非常简单。(Fundebug 注:Node 8 是 LTS,已经于 2017 年 10 月正式发布。)

原文: 6 Reasons Why JavaScript's Async/Await Blows Promises Away

译者: Fundebug

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
浅谈JavaScript中面向对象技术的模拟
Sep 25 Javascript
Prototype使用指南之ajax
Jan 10 Javascript
jQuery 1.4 15个你应该知道的新特性(译)
Jan 24 Javascript
如何动态加载外部Javascript文件
Dec 02 Javascript
为你的微信小程序体积瘦身详解
May 20 Javascript
详谈js原型继承的一些问题
Sep 06 Javascript
vue使用drag与drop实现拖拽的示例代码
Sep 07 Javascript
jquery实现回车键触发事件(实例讲解)
Nov 21 jQuery
官方推荐react-navigation的具体使用详解
May 08 Javascript
vue3.0 CLI - 3.2 路由的初级使用教程
Sep 20 Javascript
微信小程序swiper禁止用户手动滑动代码实例
Aug 23 Javascript
小程序实现tab标签页
Nov 16 Javascript
一些可能会用到的Node.js面试题
Jun 15 #Javascript
使用Vue.js 和Chart.js制作绚丽多彩的图表
Jun 15 #Javascript
通过实例讲解JS如何防抖动
Jun 15 #Javascript
js笔试题-接收get请求参数
Jun 15 #Javascript
深入了解JavaScript 的 WebAssembly
Jun 15 #Javascript
通过实例了解js函数中参数的传递
Jun 15 #Javascript
RxJS的入门指引和初步应用
Jun 15 #Javascript
You might like
编写Smarty插件在模板中直接加载数据的详细介绍
2013/06/26 PHP
解决file_get_contents无法请求https连接的方法
2013/12/17 PHP
PHP随机生成唯一HASH值自定义函数
2015/04/20 PHP
PHP中set error handler函数用法小结
2015/11/11 PHP
PHP实现通过文本文件统计页面访问量功能示例
2019/02/13 PHP
通过JQuery实现win8一样酷炫的动态磁贴效果(示例代码)
2013/07/13 Javascript
使用js简单实现了tree树菜单
2013/11/20 Javascript
JS获得浏览器版本和操作系统版本的例子
2014/05/13 Javascript
jQuery结合CSS制作动态的下拉菜单
2015/10/27 Javascript
KnockoutJS 3.X API 第四章之数据控制流foreach绑定
2016/10/10 Javascript
JS中使用 after 伪类清除浮动实例
2017/03/01 Javascript
移动端触屏幻灯片图片切换插件idangerous swiper.js
2017/04/10 Javascript
js 索引下标之li集合绑定点击事件
2018/01/12 Javascript
mpvue 如何使用腾讯视频插件的方法
2018/07/16 Javascript
JS闭包原理与应用经典示例
2018/12/20 Javascript
vue项目中使用AES实现密码加密解密(ECB和CBC两种模式)
2019/08/12 Javascript
Electron 调用命令行(cmd)
2019/09/23 Javascript
jquery 键盘事件 keypress() keydown() keyup()用法总结
2019/10/23 jQuery
vue 组件间的通信之子组件向父组件传值的方式
2020/07/29 Javascript
详解JavaScript执行模型
2020/11/16 Javascript
[01:56]林书豪DOTA2上海特级锦标赛励志短片
2016/03/05 DOTA
Python字符串处理之count()方法的使用
2015/05/18 Python
python装饰器与递归算法详解
2016/02/18 Python
PowerBI和Python关于数据分析的对比
2019/07/11 Python
python3+django2开发一个简单的人员管理系统过程详解
2019/07/23 Python
python hashlib加密实现代码
2019/10/17 Python
pygame用blit()实现动画效果的示例代码
2020/05/28 Python
python mysql中in参数化说明
2020/06/05 Python
python 调用API接口 获取和解析 Json数据
2020/09/28 Python
Opencv常见图像格式Data Type及代码实例
2020/11/02 Python
CSS3 对过渡(transition)进行调速以及延时
2020/10/21 HTML / CSS
工作过失检讨书
2014/02/23 职场文书
《海底世界》教学反思
2014/04/16 职场文书
白莲教口号
2014/06/18 职场文书
艺术学院毕业生求职信
2014/07/09 职场文书
教你使用Python pypinyin库实现汉字转拼音
2021/05/27 Python