1:介绍
我们都知道使用 Promise 能很好地解决回调地狱的问题,但如果处理流程比较复杂的话,那么整段代码将充斥着 then,语义化不明显,代码不能很好地表示执行流程,那有没有比 Promise 更优雅的异步方式呢?那就是async/await!
JavaScript 中的 async/await 是 AsyncFunction 特性 中的关键字。
注:AsyncFunction 构造函数用来创建新的 异步函数 对象,JavaScript 中每个异步函数都是 AsyncFunction 的对象。
2:基础用法
前面添加了async的函数在执行后都会自动返回一个Promise对象:
async function foo() {return 'jimmy' // Promise.resolve('jimmy')}console.log(foo()) // Promisefoo()
async函数中使用await,那么await这里的代码就会变成同步的了,意思就是说只有等await后面的Promise执行完成得到结果才会继续下去,await就是等待。请看下面的示例:
function timeout() {return new Promise(resolve => {setTimeout(() => {console.log(1)resolve()}, 1000)})}// 不加async和await是2、1 加了是1、2async function foo() {await timeout()console.log(2)}foo()
3:使用场景
假如有这样一个使用场景:需要先请求 a 链接,等返回信息之后,再请求 b 链接的另外一个资源。下面代码展示的是使用 fetch 来实现这样的需求,fetch 被定义在 window 对象中,它返回的是一个 Promise 对象。
fetch('https://blog.csdn.net/').then(response => {console.log(response)return fetch('https://juejin.im/')}).then(response => {console.log(response)}).catch(error => {console.log(error)})
虽然上述代码可以实现这个需求,但语义化不明显,代码不能很好地表示执行流程。基于这个原因,ES8 引入了 async/await,这是 JavaScript 异步编程的一个重大改进,提供了在不阻塞主线程的情况下使用同步代码实现异步访问资源的能力,并且使得代码逻辑更加清晰。
async function foo () {try {let response1 = await fetch('https://blog.csdn.net/')console.log(response1)let response2 = await fetch('https://juejin.im/')console.log(response2)} catch (err) {console.error(err)}}foo()
通过上面代码,你会发现整个异步处理的逻辑都是使用同步代码的方式来实现的,而且还支持 try catch 来捕获异常,这感觉就在写同步代码,所以是非常符合人的线性思维的。
注意点:
- await 只能在 async 标记的函数内部使用,单独使用会触发 Syntax error。
- await后面需要跟异步操作,不然就没有意义,而且await后面的Promise对象不必写then,因为await的作用之一就是获取后面Promise对象成功状态传递出来的参数。
4: 优缺点
优点:
1)方便级联调用:即调用依次发生的场景;
2)同步代码编写方式: Promise使用then函数进行链式调用,一直点点点,是一种从左向右的横向写法;async/await从上到下,顺序执行,就像写同步代码一样,更符合代码编写习惯;
3)多个参数传递: Promise的then函数只能传递一个参数,虽然可以通过包装成对象来传递多个参数,但是会导致传递冗余信息,频繁的解析又重新组合参数,比较麻烦;async/await没有这个限制,可以当做普通的局部变量来处理,用let或者const定义的块级变量想怎么用就怎么用,想定义几个就定义几个,完全没有限制,也没有冗余工作;
4)同步代码和异步代码可以一起编写: 使用Promise的时候最好将同步代码和异步代码放在不同的then节点中,这样结构更加清晰;async/await整个书写习惯都是同步的,不需要纠结同步和异步的区别,当然,异步过程需要包装成一个Promise对象放在await关键字后面;
5)基于协程: Promise是根据函数式编程的范式,对异步过程进行了一层封装,async/await基于协程的机制,是真正的“保存上下文,控制权切换……控制权恢复,取回上下文”这种机制,是对异步过程更精确的一种描述;
6)async/await是对Promise的优化: async/await是基于Promise的,是进一步的一种优化,不过在写代码时,Promise本身的API出现得很少,很接近同步代码的写法;
缺点:
Async/await 让你的代码看起来是同步的,在某种程度上,也使得它的行为更加地同步。 await 关键字会阻塞其后的代码,直到promise完成,就像执行同步操作一样。它确实可以允许其他任务在此期间继续运行,但您自己的代码被阻塞。
这意味着您的代码可能会因为大量await的promises相继发生而变慢。每个await都会等待前一个完成,而你实际想要的是所有的这些promises同时开始处理(就像我们没有使用async/await时那样)。
有一种模式可以缓解这个问题——通过将 Promise 对象存储在变量中来同时开始它们,然后等待它们全部执行完毕。如果想更加深入的了解,请参考 MDN[1]
5:声明async函数的几个方法
//普通的函数声明async function A(){}//声明一个函数表达式let A=async function(){}//async形式的箭头函数let A=async ()=>{}
6: 实例应用
// 路由守卫router.beforeEach(async (to: RouteLocationNormalized) => {// canUserAccess() 返回 `true` 或 `false`// eslint-disable-next-line no-return-awaitawait canUserAccess(to, router);});// 控制器调用与server中查询数据exports.getBlogList =async (ctx,next)=>{return ctx.body = await ArticleServer.getBlogListServer();}exports.getBlogListServer= async ()=>{let where={id:'1'}let blogList= await articleBlogDa.findAll({where:where});console.log("哈哈哈"+blogList);return blogList;}
7:关键点小结
- await关键字必须位于async函数内部
- await关键字后面需要一个promise对象(不是的话就调用resolve转换它)
- await关键字的返回结果就是其后面Promise执行的结果,可能是resolved或者rejected的值,注意最后执行完的是个值。
- 不能在普通箭头函数中使用await关键字,需要在箭头函数前面添加async
- await用来串行的执行异步操作,现实现并行可以考虑promise.all
