这篇文章拖了好久好久了... 上一篇的后续,从实际案例中进一步理解js异步。
上一篇: 【Promise】从小程序简单的登录逻辑说promise
如果还没看过上一篇可以先看看,从现实需求中理解promise是一个很好的思路。
接着上一篇末尾提出的问题,想拿到promise返回的值怎么办,来说一说async/await
需求
小程序登录调用wx.login
接口,这个接口是异步的,返回一个code,后端用这个code换取session_key
。
具体的业务逻辑就不多说了,上一篇讲了一下。
上一篇文章中,我们已经封装了一个函数把小程序异步写法换成promise写法,这非常有用。
但问题是,因为code是后续逻辑必须的,所以要保证后面的代码在执行的时候已经拿到code。
直接使用Promise解决
再说说为什么会有回调地狱
题外话,其实这种异步调用中获取的内容是后续逻辑的必要条件的情况,就是产生回调地狱的原因。
原始的解决方案就是不断地嵌套回调来保证逻辑是按序执行的。
Promise的出现就是为了将这种不断嵌套的回调优化为链式调用,不光逻辑清晰了,函数的执行上下文也清晰了。
所以Promise是能解决我们的需求的。只需要把后续的逻辑一步步写在then里面就ok了。
后续逻辑中还有需要处理的异步,就再写一个then,总之then then就完事了。
这样做的好处是,可以用catch做异常捕获。
坏处自然很明显,全部的逻辑都要写在then里面,相当于你的逻辑代码就一条promise语句,丑。
更关键的是,如果是多个then的链式调用,其实Pormise的写法并不易于维护,也一点不优雅,我们期望的是能像写同步代码一样写异步。
像写同步代码一样写异步这就是为什么async/await
被称为异步的完美解决方案。
async/await
先理解一句话,async/await
本质就是Promise的语法糖。
我认为理解async/await
的最好角度是事件循环,看到有大佬写的一篇文章很不错, 虽然他标题是反过来的23333,但是值得一看。
从event loop到async await来了解事件循环机制
我在这里也简单讲一些要点
理解async/await
macro-task和micro-task
这里就不能只单纯局限于事件队列的表层概念了,一定要理解macro-task和micro-task。
简单来说,macro-task包含了:
- 同步代码
- setTimeout,setInterval和内部的回调函数
micro-task包含了:
- Promise的then
- process.nextTick
注意new Promise的部分是同步代码
事件循环
- 同步代码作为一轮macro-task执行
- 遇到setTimeout,setInterval将回调推入macro-task队列
- 遇到Promise,把then推入当前macro-task的micro-task队列
- 本轮macro-task全部执行后,执行全部micro-task
- 执行下一轮macro-task
- ...
这里的macro-task就是平时说事件循环时说的同步栈和事件队列。
不同的是,Promise的异步任务并不会被推入和setTimeout相同的任务队列,而是有专门的micro-task队列,并且每轮macro-task执行完之后都会清空micro-task队列。
换句话说,then作为micro-task会优先于setTimeout的回调执行。
上面的链接里有举例分析,我就不说了。
async/await
机制
首先明确一下概念
- async是用来声明函数的,
async function() {...}
,就是声明异步函数。 - await是写在表达式前面的,字面意思,等待表达式完成。表达式是所有的js表达式都行。
- await必须写在async里面
为什么这是Promise的语法糖?
也有说是generator的语法糖,我对js的generator理解不够,有新的理解会来更新这篇文章
async函数实际返回一个Promise
async function f() {console.log(1)}
f()
// Promise {<resolved>: undefined}
复制代码
所以其实这就是Promise的封装。
说到这里还并不能很好得理解到底这糖到底甜在哪
下面我们通过await的机制进一步来理解
await的机制:
- 如果等待的是一个同步的表达式,那直接拿到结果
- 如果等待的是一个Promise,阻塞后续代码,等待Promise resolve,并将resolve的值作为表达式计算结果,可以进行赋值。
bb了这么多,终于到了解决开头提出来的问题的地方。想拿到promise返回的值怎么办?用await。
这里直接把我写的登录逻辑贴出来
// 登录的全局方法
// 该函数需要指定this指针
export async function login() {
console.log('login...');
var login = promisify(mpvue.login)
var code = await login().then(res => res.code)
console.log('get login code: ' + code)
this.$http.post(
this.$store.getters.loginAPI,
{
code: code
}
).then( res => {
handleLoginRes.call(this, res)
}).catch(() => {
...
})
}
复制代码
如果用Promise来解决
function login() {
console.log('login...');
var login = promisify(mpvue.login)
login().then(res => {
var code = res.code
console.log('get login code: ' + code)
this.$http.post(
this.$store.getters.loginAPI,
{
code: code
}).then( res => {
handleLoginRes.call(this, res)
}).catch(() => {
...
})
}
})
复制代码
这里只需要等待一个code,区别不大并不是那么明显,如果需要再等待两三个值呢,就成了各种资料里说的经典案例,我就不写了。
总之,await得以让Promise的链式调用转换为和同步代码一样的写法,看着就很爽很顺滑。
从async/await
实现深入理解
我在上面给阻塞加粗了,这个阻塞和平时说的阻塞不太一样,这就涉及到await的实现了,我也还没搞明白。这部分等我找时间搞懂了更。