JS 异步接口调用介绍
Js 单线程模型
JavaScript 语言的一大特点就是单线程,也就是说,同一个时间只能做一件事。这样设计的方案主要源于其语言特性,因为 JavaScript 是浏览器脚本语言,它可以操纵 DOM ,可以渲染动画,可以与用户进行互动,如果是多线程的话,执行顺序无法预知,而且操作以哪个线程为准也是个难题。
所以,为了避免复杂性,从一诞生,JavaScript就是单线程,这已经成了这门语言的核心特征,将来也不会改变。
在 HTML5 时代,浏览器为了充分发挥 CPU 性能优势,允许 JavaScript 创建多个线程,但是即使能额外创建线程,这些子线程仍然是受到主线程控制,而且不得操作 DOM,类似于开辟一个线程来运算复杂性任务,运算好了通知主线程运算完毕,结果给你,这类似于异步的处理方式,所以本质上并没有改变 JavaScript 单线程的本质。
任务队列
所有任务可以分成两种,一种是 同步任务(synchronous),另一种是 异步任务(asynchronous) 。
同步任务指的是,在主线程上排队执行的任务,只有前一个任务执行完毕,才能执行后一个任务。
所以,当在执行过程中遇到一些类似于 setTimeout 等异步操作的时候,会交给浏览器的其他模块进行处理,当到达 setTimeout 指定的延时执行的时间之后,回调函数会放入到任务队列之中。
当然,一般不同的异步任务的回调函数会放入不同的任务队列之中。等到调用栈中所有任务执行完毕之后,接着去执行任务队列之中的回调函数
JavaScript |
大家可以猜测下它的输出内容?
输出结果是1,2,set2, setTimeOut
那你可能会好奇为什么结果是这样的呢,为了讲清楚这个问题答案,我们先要搞清楚两个任务,一个是macro-task(宏任务),一个micro-task(微任务)
macro-task(宏任务)
大概包括:script(整体代码), setTimeout, setInterval, setImmediate(NodeJs), I/O, UI rendering。
micro-task(微任务)
大概包括: process.nextTick(NodeJs), Promise, 来自不同任务源的任务会进入到不同的任务队列。
执行顺序是一开始先
主循环下来,先把微队列任务执行完成,在执行宏队列的一个任务,这个宏任务完成在去执行微队列,就这样依次循环执行
有了上边的基础,我们接下来在看下下边代码执行过程
JavaScript |
Promise的构造方法的第一个方法会立即执行的,后边then会放到微任务队列里办
第一遍执行完成后会输出
1 2 3
此时的宏队列与微队列是
宏队列 | 微队列 |
setTimeout | then |
一开始js脚本是执行的宏队列,这里轮到了微队列,会先执行Promise里边的then,此时微队列空了,接着执行宏队列,后续输出的应该是
5 4
最终输出的是
1 2 3 5 4
在看下下边这个复杂的
JavaScript |
首先我们可以看到script有2个,我们暂且叫它script1,
宏队列 | 微队列 |
script1 | |
script2 |
宏队列开始执行script1 的脚本后,Promise构造方法会立即执行,所以会先打印出来
1 2 3
此时对列为
宏队列 | 微队列 |
script2 | then5 |
setTimeout4 |
script1 宏任务执行完成后,接着要把微队列任务都要执行完成,接着执行5
1 2 3 5
宏队列 | 微队列 |
script2 | |
setTimeout4 |
执行script2
1 2 3 5 6
宏队列 | 微队列 |
setTimeout4 | then7 |
接着执行微队列then
1 2 3 5 6 7
就剩最后一个宏任务setTimeout4了
1 2 3 5 6 7 4
如果现在大家搞清楚了,js 的异步多少会有一些了解
接下来介绍下async 与 await
async
async 函数是使用async关键字声明的函数。async 函数是 AsyncFunction 构造函数的实例,并且其中允许使用 await 关键字。async 和 await 关键字让我们可以用一种更简洁的方式写出基于 Promise 的异步行为,而无需刻意地链式调用 promise。
JavaScript |
我们来分析下这个代码
宏队列 | 微队列 |
asyncCall() | |
执行asyncCall()
输出calling
宏队列 | 微队列 |
resolveAfter2Seconds() | |
宏队列 | 微队列 |
2s以后setTimeout | |
宏队列 | 微队列 |
console.log(result) | |
最终输出结果
Calling
#2秒以后输出
resolve
Promise
Promise 对象用于表示一个异步操作的最终完成(或失败)及其结果值。
一个 Promise 必然处于以下几种状态之一:
- 待定(pending):初始状态,既没有被兑现,也没有被拒绝。
- 已兑现(fulfilled):意味着操作成功完成。
- 已拒绝(rejected):意味着操作失败。
Promise.reject(reason)
返回一个状态为已拒绝的 Promise 对象,并将给定的失败信息传递给对应的处理函数。
Promise.resolve(value)
返回一个状态由给定 value 决定的 Promise 对象。如果该值是 thenable(即,带有 then 方法的对象),返回的 Promise 对象的最终状态由 then 方法执行结果决定;否则,返回的 Promise 对象状态为已兑现,并且将该 value 传递给对应的 then 方法。
await
await 操作符用于等待一个 Promise 兑现并获取它兑现之后的值。它只能在异步函数或者模块顶层中使用。
当函数执行到 await 时,被等待的表达式会立即执行,所有依赖该表达式的值的代码会被暂停,并推送进微任务队列(microtask queue)。然后主线程被释放出来,用于事件循环中的下一个任务。即使等待的值是已经敲定的 promise 或不是 promise,也会发生这种情况。例如,考虑以下代码:
JavaScript |
可以转化成
JavaScript |
可以看下加黄色的字体,await可以转成 Promise.then,会被放到微任务队列
JavaScript |