你真的会用 ES6 的 async 异步函数吗?文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/8523.html
1、async 介绍
先上 MDN 介绍:developer.mozilla.org/zh-CN/docs/…文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/8523.html
async function 用于声明 一个 返回 AsyncFunction 对象的异步函数。异步函数是值通过事件循环异步执行的函数,它会通过一个隐式的 Promise 返回其结果。如果你的代码使用了异步函数,它的语法和结构更像是标准的同步函数文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/8523.html
人工翻译:async 关键字是用于表示一个函数里面有异步操作的含义。它通过返回一个 Promise 对象来返回结果它的最大的特点是:通过 async / await 将异步的操作,但是写法和结构却是和我们平时写的(同步代码)是一样文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/8523.html
2、示范
// 一般我们会把所有请求方法都定义在一个文件里,这里定义一个方法来模拟我们的日常请求
function fetch() {
axios.get('/user?ID=12345')
.then(function (response) {
console.log(response);
})
.catch(function (error) {
console.log(error);
});
};
// 然后在需要它的地方调用它
async function getUserInfo() {
const info = await fetch();
return info;
}
getUserInfo().then(info => console.log(info));
复制代码
我们可以看到,整个过程非常直观和清晰,语句语义非常明确,整个异步操作看起来就像是同步一样。如果看完上面的流程没有问题的话,那我们接下来继续深入的了解一下。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/8523.html
3、async Promise setTimeout(定时器) 的结合使用情况
接下来给大家演示一道题目,这道题是我当时面某条的面试题,估计和多人也见过,这道题非常经典而且使用场景页非常多,研究意义非常大,那么我在这里就给大家分享一下。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/8523.html
求下面的输出结果:
async function async1(){
console.log('async1 start')
await async2()
console.log('async1 end')
}
async function async2(){
console.log('async2')
}
console.log('script start')
setTimeout(function(){
console.log('setTimeout')
},0)
async1();
new Promise(function(resolve){
console.log('promise1')
resolve();
}).then(function(){
console.log('promise2')
})
console.log('script end')
复制代码
这里一共有 8 条 log 语句,先别复制到控制台上,大家给20秒钟的时间默念一下输出的顺序。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/8523.html
1..2.. .. .. 20文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/8523.html
我先给上正确的答案:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/8523.html
script start
async1 start
async2
promise1
script end
promise2
async1 end
setTimeout
复制代码
如果你的答案和上面的正确答案有所偏差,那么说明你对 async / await 的理解还是不够深刻,希望你阅读完我的这篇文章之后可以直面各种同步异步问题了(嘻嘻,这还不点个赞嘛)文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/8523.html
我们再来回顾一下 MDN 对 async / await 的描述:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/8523.html
当调用一个
async
函数时,会返回一个Promise
对象。当这个async
函数返回一个值时,Promise
的 resolve 方法会负责传递这个值;当async
函数抛出异常时,Promise
的 reject 方法也会传递这个异常值。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/8523.html
async
函数中可能会有await
表达式,这会使async
函数暂停执行,等待Promise
的结果出来,然后恢复async
函数的执行并返回解析值(resolved)。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/8523.html
async
/await
的用途是简化使用 promises 异步调用的操作,并对一组Promises
执行某些操作。正如Promises
类似于结构化回调,async
/await
类似于组合生成器和 promises。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/8523.htmlawait文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/8523.html
await
操作符用于等待一个Promise
对象。它只能在异步函数async function
中使用。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/8523.html[return_value] = await expression; 复制代码
await 表达式会暂停当前
async function
的执行,等待 Promise 处理完成。若 Promise 正常处理(fulfilled),其回调的resolve函数参数作为 await 表达式的值,继续执行async function
。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/8523.html若 Promise 处理异常(rejected),await 表达式会把 Promise 的异常原因抛出。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/8523.html
另外,如果 await 操作符后的表达式的值不是一个 Promise,则返回该值本身。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/8523.html
其中非常重要的一句是:遇到 await 表达式时,会让 async 函数 暂停执行,等到 await 后面的语句(Promise)状态发生改变(resolved或者rejected)之后,再恢复 async 函数的执行(再之后 await 下面的语句),并返回解析值(Promise的值)文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/8523.html
这么多 Promise 相关的内容是因为async / await 是建立在 Promise 的基础上的呀~~文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/8523.html
然后再来回头看我们的题目,会发现,有点不对劲啊文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/8523.html
async1 end
promise2
复制代码
那是因为还有一个Promise.resolve 的点没有考虑,这也是我中招的点文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/8523.html
4、分析过程
- 定义一个异步函数 async1
- 定义一个异步函数 async2
- 打印 ‘script start’ // *1
- 定义一个定时器(宏任务,优先级低于微任务),在0ms 之后输出
- 执行异步函数 async1
- 打印 'async1 start' // *2
- 遇到await 表达式,执行 await 后面的 async2
- 打印 'async2' // *3
- 返回一个 Promise,跳出 async1 函数体
- 执行 new Promise 里的语句
- 打印 ‘promise1‘ // *4
- resolve() , 返回一个 Promise 对象,把这个 Promise 压进队列里
- 打印 ’script end' // *5
- 同步栈执行完毕
- 回到 async1 的函数体,async2 函数没有返回 Promise,所以把要等async2 的值 resolve,把 Promise 压进队列
- 执行 new Promise 后面的 .then,打印 ’promise2‘ // *6
- 回到 async1 的函数体,await 返回 Promise.resolve() ,然后打印后面的 ’async1 end‘ // *7
- 最后执行定时器(宏任务) setTimeout,打印 ’setTimeout‘ // *8
我对这段代码的过程分析大致如上(如果有什么理解不对的地方请指出),这里有很关键而且是大家容易理解错误的点是:很多人以为 await 会一直等待后面的表达式执行完之后才会执行后续代码,实际上 await 是会先执行后面的表达式,然后返回一个Promise,接着就跳出整个 async 函数来执行后面的代码,也就是说执行到 await 的时候,会有一个 让出线程 的操作。等后面的同步站执行完了之后,又会回到 async 函数中等待 await 表达式的返回值,如果不是一个 Promise 对象,则会有一个期待它 resolve 成为一个 Promise对象的过程,然后继续执行 async 函数后面的代码,直到是一个 Promise 对象,则把这个 Promise 对象放入 Promise 队列里。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/8523.html
所以说 ,’async1 end' 和‘promise2‘
这个不注意就会出错的难点就是这样文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/8523.html
那么现在,我们是不是大致上对async / await 理解了呢,我们来改一下这道题再来看看,把 async2 改造一下文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/8523.html
async function async1(){
console.log('async1 start')
await async2()
console.log('async1 end')
}
function async2(){ // 去掉了 async 关键字
console.log('async2');
}
console.log('script start')
setTimeout(function(){
console.log('setTimeout')
},0)
async1();
new Promise(function(resolve){
console.log('promise1')
resolve();
}).then(function(){
console.log('promise2')
})
console.log('script end')
复制代码
这次大家能做对了吗~文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/8523.html
5、日常常用示例
上面写了那么多,只是为了方便大家对于异步函数的理解,文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/8523.html
下面给一些我们日常开发中使用异步函数的例子。一般来说,我们有一个业务需要分不完成,每个步骤都是异步的,并且严重依赖于上一步的执行结果,稍有不慎就会进入回调地狱(callback hell)了,这种情况下,我们可以用 async / await 来完成文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/8523.html
// 比如在这里场景,我们提交数据的时候先判定用户是否有这个权限,然后再进行下一步动作
async function submitData(data) {
const res = await getAuth(); // 获取授权状态
if (res....) {
const data = await submit(data);
}
toast(data.message);
}
复制代码
这样就可以保证两个操作的先后顺序文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/8523.html
或者是在 Vue 中,一些初始化的操作文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/8523.html
async created() {
const res = await this.init(); // 获取列表等操作
const list = await this.getPage(); // 分页请求等
}
复制代码
但是在使用过程中,我们会发现刚从回调地狱中解救,然后就陷入 async / await 地狱的诞生文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/8523.html
举一个例子:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/8523.html
async created() {
const userInfo = await this.getUserInfo(); // 获取用户数据
const list = await this.getNewsList(); // 获取文章数据
}
复制代码
表面上看,这段语法是正确的,但并不是一个优秀实现,因为它把两个没有先后顺序的一部操作强行变成同步操作了,因为这里的代码是一行接着一行执行的,想一下,我们没有必要在获取用户数据之后才去获取文章数据,它们的工作是可以同时进行的文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/8523.html
这里给出一些常用的并发执行的实例文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/8523.html
async created() {
const userInfo = this.getUserInfo(); // 它们都会返回 Promise 对象
const list = this.getNewsList();
await userInfo;
await list;
// ...do something
}
// 如果有很多请求的情况下可以使用 Promise.all
async created() {
Promise.all([this.getUserInfo(), this.getNewsList()]).then(()=> {
// ...do something
});
}
复制代码
5、图例
6、小结
1、异步的终极解决方案文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/8523.html
2、看起来像同步的异步操作文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/8523.html
3、便捷的捕获错误和调试文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/8523.html
4、支持并发执行文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/8523.html
5、要知道避免 async / await 地狱文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/8523.html
7、写在最后
好了,关于async 异步函数的不完全指南就说到这里了,上面所提及的内容,可能也就比较浅显的内容。建议大家熟练使用它,在日常开发中多使用多总结才会有沉淀的效果,都是要靠自己多练,才能熟悉使用,熟能生巧!文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/8523.html
作者:Croc_wend
链接:https://juejin.im/post/5c0397186fb9a049b5068e54
来源:掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/8523.html