同步和异步的区别不是这段代码能不能很快执行完,拿到结果。
同步任务,比如for循环也可能要长时间执行。
最关键的区别就是js引擎是否能把那些需要很长时间执行,不能立即拿到结果的任务交出去(交出去的动作是同步完成的),让宿主环境去执行,后续通过回调拿到执行的结果,自己则接着去处理后续的任务。
至于那些不能交出去,只能js引擎自己做的任务,就是同步任务。
常见的异步任务有两种:
定时器
ajax
上面的理解还有一点问题,不是说js引擎交给宿主环境就完全不管了,不是这样的,异步任务执行是需要js引擎和宿主环境配合完成的。异步任务执行的过程可以拆分成下面四个步骤:
拿 setTimeout 来举例:
1.提交阶段(由js引擎同步执行)
当JS引擎执行到 setTimeout(callback, 1000) 这行代码时,它当场、立刻、同步地完成了一件事:把 callback 函数和 1000ms 的时长提交给浏览器的定时器线程。 这个提交动作本身就是瞬间的同步任务,做完JS引擎就立刻去执行下一行代码了。
- 宿主环境执行阶段
现在,浏览器的定时器线程开始工作了。它负责计时这1000毫秒。这件事JS引擎完全不管,也管不了。 这就是我前面说的"交出去"的部分,这部分确实不是JS引擎执行的。
- 回调排队阶段(宿主环境执行)
1000毫秒到了!定时器线程不会直接把回调插入JS引擎去执行(那会搞乱正在运行的同步代码)。它会做一件事:把 callback 这个函数,放到宏任务(Task)队列的末尾去排队。
- 回调执行阶段(由js引擎执行)
等所有同步代码执行完,事件循环会去检查任务队列,看到有个排队的 callback,就把它取出来,由JS引擎来执行。
一个 setTimeout 异步任务,宿主环境执行了计时,JS引擎执行了提交和最终的回调。
同理拆解 Ajax 网络请求
fetch('http://api.example.com').then(callback)
1.调用与提交(JS引擎,同步):JS引擎调用 fetch,把请求对象提交给浏览器的网络线程,然后立刻返回一个 Promise,继续执行后面的代码。
-
后台执行(宿主环境,异步):浏览器的网络线程负责建立连接、发送请求、等待响应...整个过程JS引擎完全不参与。
-
回调通知与排队(宿主环境):服务器响应返回了。网络线程会把 callback(即 .then 里的回调)包装成一个微任务,放入微任务队列。
-
回调执行(JS引擎):当前同步代码执行完毕,JS引擎会清空微任务队列,执行那个 callback,处理拿到的响应数据。
promise是怎么解决异步任务的呢
promise就是把异步任务封装到它的参数函数里面,通过rraolve或者reject把异步任务的结果交给promise实例,至于后面我要怎么处理这个结果,什么时候处理,我都有完全的控制权
1.传给Promise的参数函数会立即同步执行,并且会执行完,也就是把它里面的异步任务交出去之后,会再继续执行完这个函数里面的后续代码。
2.在参数函数里面碰到异步任务的时候,还是和之前一样,js引擎会把任务交给宿主环境,js引擎就回去处理别的
3.等到异步任务处理完,就通过resolve或reject把异步任务的结果放到promise实例中。
async和await语法糖
用async定义的函数就是可以把函数返回的数据放到promise实例对象中,并把这个实例返回。
await就是可以让我们不用调用then,通过回调去拿promise实例中的数据,而是通过赋值的方式拿到,
所以经常见到await放在一个异步函数调用的前面,通过赋值的方式去拿异步函数返回的promise中的结果。
在async定义的函数里面碰到await会怎么处理呢,那这个async函数的调用执行就会被打断,await后面的代码也会被放到任务队列中,等到await拿到它后面的异步函数返回的promise中的数据,并且js引擎空闲时,再去任务队列执行后续代码
三种处理异步任务的方式,只是写法不一样,他们的目的都是一样的,都是让依赖异步操作结果得代码,在异步操作之后执行。
async和await的作用就是让我们可以用同步代码的方式,把异步任务和依赖异步任务结果的代码封装到一个函数里面,并且await的作用就是把后面依赖异步任务结果的操作编程回调函数放到任务队列中。
传统的方式就是把这些依赖异步任务的操作放到异步任务的回调函数中,
而promise的做法就是把异步任务的结果放到promise实例对象中,再把那些依赖异步任务的操作放到then的回调参数中。