ES6前端编程: async 异步函数 不完全使用攻略

2018-12-0409:11:53WEB前端开发Comments2,316 views字数 4801阅读模式

你真的会用 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.html

await文章源自菜鸟学院-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、分析过程

  1. 定义一个异步函数 async1
  2. 定义一个异步函数 async2
  3. 打印 ‘script start’ // *1
  4. 定义一个定时器(宏任务,优先级低于微任务),在0ms 之后输出
  5. 执行异步函数 async1
    1. 打印 'async1 start' // *2
    2. 遇到await 表达式,执行 await 后面的 async2
      1. 打印 'async2' // *3
    3. 返回一个 Promise,跳出 async1 函数体
  6. 执行 new Promise 里的语句
    1. 打印 ‘promise1‘ // *4
    2. resolve() , 返回一个 Promise 对象,把这个 Promise 压进队列里
  7. 打印 ’script end' // *5
  8. 同步栈执行完毕
  9. 回到 async1 的函数体,async2 函数没有返回 Promise,所以把要等async2 的值 resolve,把 Promise 压进队列
  10. 执行 new Promise 后面的 .then,打印 ’promise2‘ // *6
  11. 回到 async1 的函数体,await 返回 Promise.resolve() ,然后打印后面的 ’async1 end‘ // *7
  12. 最后执行定时器(宏任务) 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、图例

ES6前端编程: async 异步函数 不完全使用攻略

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

文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/8523.html
  • 本站内容整理自互联网,仅提供信息存储空间服务,以方便学习之用。如对文章、图片、字体等版权有疑问,请在下方留言,管理员看到后,将第一时间进行处理。
  • 转载请务必保留本文链接:https://www.cainiaoxueyuan.com/gcs/8523.html

Comment

匿名网友 填写信息

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定