JS 异步接口调用介绍

Js 单线程模型

JavaScript 语言的一大特点就是单线程,也就是说,同一个时间只能做一件事。这样设计的方案主要源于其语言特性,因为 JavaScript 是浏览器脚本语言,它可以操纵 DOM

android js调用异步 js异步调用接口_前端

 

所以,为了避免复杂性,从一诞生,JavaScript就是单线程,这已经成了这门语言的核心特征,将来也不会改变。

在 HTML5 时代,浏览器为了充分发挥 CPU 性能优势,允许 JavaScript 创建多个线程,但是即使能额外创建线程,这些子线程仍然是受到主线程控制,而且不得操作 DOM,类似于开辟一个线程来运算复杂性任务,运算好了通知主线程运算完毕,结果给你,这类似于异步的处理方式,所以本质上并没有改变 JavaScript

任务队列

所有任务可以分成两种,一种是 同步任务(synchronous),另一种是 异步任务(asynchronous)

同步任务指的是,在主线程上排队执行的任务,只有前一个任务执行完毕,才能执行后一个任务。

所以,当在执行过程中遇到一些类似于 setTimeout 等异步操作的时候,会交给浏览器的其他模块进行处理,当到达 setTimeout

当然,一般不同的异步任务的回调函数会放入不同的任务队列之中。等到调用栈中所有任务执行完毕之后,接着去执行任务队列之中的回调函数

JavaScript
  setTimeout(() => {
     console.log('setTimeout')
   }, 22)//定时22ms,后执行setTimeout
   for (let i = 0; i++ < 2;) {
     i === 1 && console.log('1')
   }
   setTimeout(() => {
     console.log('set2')
   }, 20)//定时20ms 后执行console.log('set2')
   //下边循环时间假定是50ms
   for (let i = 0; i++ < 100000000;) {
     i === 99999999 && console.log('2')
   }


 

大家可以猜测下它的输出内容?

输出结果是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,  来自不同任务源的任务会进入到不同的任务队列。
 

执行顺序是一开始先

android js调用异步 js异步调用接口_javascript_02

 

主循环下来,先把微队列任务执行完成,在执行宏队列的一个任务,这个宏任务完成在去执行微队列,就这样依次循环执行

有了上边的基础,我们接下来在看下下边代码执行过程

JavaScript
 setTimeout(() => {
   console.log(4)
 }, 0);
 //Promise构造方法先执行
 new Promise((resolve) =>{
   console.log(1);
   for (var i = 0; i < 10000000; i++) {
     i === 9999999 && resolve();
   }
   console.log(2);
 }).then(() => {
   console.log(5);
 });
 console.log(3);


 

Promise的构造方法的第一个方法会立即执行的,后边then会放到微任务队列里办

第一遍执行完成后会输出

1 2 3

此时的宏队列与微队列是

宏队列

微队列

setTimeout

then

一开始js脚本是执行的宏队列,这里轮到了微队列,会先执行Promise里边的then,此时微队列空了,接着执行宏队列,后续输出的应该是

5 4

最终输出的是

1 2 3 5 4

在看下下边这个复杂的

JavaScript
 <script>
   setTimeout(() => {
     console.log(4)
   }, 0);
   new Promise((resolve) => {
     console.log(1);
     for (var i = 0; i < 10000000; i++) {
       i === 9999999 && resolve();
     }
     console.log(2);
   }).then(() => {
     console.log(5);
   });
   console.log(3);
 </script>
 <script>
   console.log(6)
   new Promise((resolve) => {
     resolve()
   }).then(() => {
     console.log(7);
   });
 </script>

首先我们可以看到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
 function resolveAfter2Seconds() {
   return new Promise(resolve => {
     setTimeout(() => {
       resolve('resolved');
     }, 2000);
   });
 }

 async function asyncCall() {
   console.log('calling');
   const result = await resolveAfter2Seconds();
   console.log(result);
   // Expected output: "resolved"
 }

 asyncCall();


 

我们来分析下这个代码

宏队列

微队列

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

await

await 操作符用于等待一个 Promise 兑现并获取它兑现之后的值。它只能在异步函数或者模块顶层中使用。

当函数执行到 await 时,被等待的表达式会立即执行,所有依赖该表达式的值的代码会被暂停,并推送进微任务队列(microtask queue)。然后主线程被释放出来,用于事件循环中的下一个任务。即使等待的值是已经敲定的 promise 或不是 promise,也会发生这种情况。例如,考虑以下代码:

JavaScript
 async function foo(name) {
   console.log(name, "start");
   await console.log(name, "middle");
   console.log(name, "end");
 }

 foo("First");
 foo("Second");

 // First start
 // First middle
 // Second start
 // Second middle
 // First end
 // Second end

可以转化成

JavaScript
 function foo(name) {
   return new Promise((resolve) => {
     console.log(name, "start");
resolve(console.log(name, "middle"));
   }).then(() => {
     console.log(name, "end");
   });
 }

可以看下加黄色的字体,await可以转成 Promise.then,会被放到微任务队列

JavaScript
 let i = 0;

 queueMicrotask(function test() {
   i++;
   console.log("microtask", i);
   if (i < 3) {
     queueMicrotask(test);
   }
 });

 (async () => {
   console.log("async function start");
   for (let i = 1; i < 3; i++) {
     await null;
     console.log("async function resume", i);
   }
   await null;
   console.log("async function end");
 })();

 queueMicrotask(() => {
   console.log("queueMicrotask() after calling async function");
 });

 console.log("script sync part end");
 //async function start
 //script sync part end
 //microtask 1
 //async function resume 1
 //queueMicrotask() after calling async function
 //microtask 2
 //sync function resume 2
 //microtask 3
 //async function end