zoukankan      html  css  js  c++  java
  • Promise里的代码为什么比setTimeout先执行

    当浏览器或者Node拿到一段代码时首先做的就是传递给JavaScript引擎,并且要求它去执行。

    然而,执行 JavaScript 并非一锤子买卖,宿主环境当遇到一些事件时,会继续把一段代码传递给 JavaScript 引擎去执行,此外,我们可能还会提供 API 给 JavaScript 引擎,比如 setTimeout 这样的 API,它会允许 JavaScript 在特定的时机执行。

    所以,我们首先应该形成一个感性的认知:一个 JavaScript 引擎会常驻于内存中,它等待着我们(宿主)把 JavaScript 代码或者函数传递给它执行。

    由于我们这里讲的是JavaScript 语言,我们把宿主发起的任务称为宏观任务,把 JavaScript 引擎发起的任务称为微观任务。

    宏观和微观任务

    JavaScript 引擎等待宿主环境分配宏观任务,在操作系统中,通常等待的行为都是一个事件循环,所以在 Node 术语中,也会把这个部分称为事件循环。我们用伪代码来表示,大概就是:

    while (TRUE) {
       r = wait();
       execute(r);
     }

    我们可以看到,整个循环做的事情基本上就是反复“等待 - 执行”。当然,实际的代码中并没有这么简单,还有要判断循环是否结束、宏观任务队列等逻辑。

    有了宏观任务和微观任务机制,我们就可以实现 JS 引擎级和宿主级的任务了,例如:Promise 永远在队列尾部添加微观任务。setTimeout 等宿主 API,则会添加宏观任务。在执行完一个宏观任务后再执行后一个宏观任务。

    接下来我们介绍一下 Promise。

    Promise 是 JavaScript 语言提供的一种标准化的异步管理方式,它的总体思想是,需要进行 io、等待或者其它异步操作的函数,不返回真实结果,而返回一个“承诺”,函数的调用方可以在合适的时机,选择等待这个承诺兑现(通过 Promise 的 then 方法的回调)。(建议不是很了解promise的可以去看一下阮一峰老师的ES6标准入门

    Promise 的 then 回调是一个异步的执行过程,下面我们就来研究一下 Promise 函数中的执行顺序,我们来看一段代码示例:

             

    1  var r = new Promise(function(resolve, reject) {
    2    console.log("a");
    3    resolve()
    4  });
    5  r.then(() => console.log("c"));
    6  console.log("b")

     

    我们执行这段代码后,注意输出的顺序是 a b c。在进入 console.log(“b”) 之前,毫无疑问 r 已经得到了 resolve,但是 Promise 的 resolve 始终是异步操作,所以 c 无法出现在 b 之前。

    接下来我们试试跟 setTimeout 混用的 Promise。为了理解微任务始终先于宏任务,将Promise改成耗时1秒。

              

     1 setTimeout(()=>console.log("d"), 0)
     2 var r = new Promise(function(resolve, reject){
     3   resolve()
     4 });
     5 r.then(() => { 
     6      var begin = Date.now();
     7   while(Date.now() - begin < 1000);
     8   console.log("c1") 
     9   new Promise(function(resolve, reject){
    10      resolve()
    11   }).then(() => console.log("c2"))
    12 });

     

    这里我们强制了 1 秒的执行耗时,这样,我们可以确保任务 c2 是在 d 之后被添加到任务队列。

    我们可以看到,即使耗时一秒的 c1 执行完毕,再 enque 的 c2,仍然先于 d 执行了,这很好地解释了微任务优先的原理

    通过一系列的实验,我们可以总结一下如何分析异步执行的顺序:

    1、首先我们分析有多少个宏任务;
    2、在每个宏任务中,分析有多少个微任务;
    3、根据调用次序,确定宏任务中的微任务执行次序;
    4、根据宏任务的触发规则和调用次序,确定宏任务的执行次序;(同一个宏任务下的微任务始终于这个宏任务前执行:可参考:https://jakearchibald.com/2015/tasks-microtasks-queues-and-schedules/ )
    5、确定整个顺序。

            

    1 function sleep(duration) {
    2   return new Promise(function(resolve, reject) {
    3     console.log("b");
    4     setTimeout(resolve,duration);
    5   })
    6 }
    7 console.log("a");
    8 sleep(5000).then(()=>console.log("c"));

     

    这是一段非常常用的封装方法,利用 Promise 把 setTimeout 封装成可以用于异步的函数。

    我们首先来看,setTimeout 把整个代码分割成了 2 个宏观任务,这里不论是 5 秒还是 0 秒,都是一样的。

    第一个宏观任务中,包含了先后同步执行的 console.log(“a”); 和 console.log(“b”);。

    setTimeout 后,第二个宏观任务执行调用了 resolve,然后 then 中的代码异步得到执行,所以调用了 console.log(“c”),最终输出的顺序才是: a b c。

    这里应该更能了解Promise和setTimeout以及其他代码的执行顺序了。

    本文参考于  winter 老师的重学前端课程,有兴趣的小伙伴也可以去了解一下。

  • 相关阅读:
    【log】AOP配置日志
    【AOP】【log】Spring AOP 完成日志记录
    【java】staitc
    【spring】Spring3注释装配的最佳实践持久层
    【LDA】lda模型和java代码
    【tomcat】tomcat使用jndi,hibernate自动生成的DAO类用了jndi
    【spring】Spring基于 Annotation 的简单介绍
    【bayes】贝叶斯学派和频率学派
    【phi】balance
    【spring】【log】spring mvc 中使用log4j
  • 原文地址:https://www.cnblogs.com/chase-star/p/11016256.html
Copyright © 2011-2022 走看看