当浏览器或者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 函数中的执行顺序,我们来看一段代码示例:

          var r = new Promise(function(resolve, reject){
            console.log("a");
            resolve()
            });
          r.then(() => console.log("c"));
          console.log("b")

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

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

          setTimeout(()=>console.log("d"), 0)
          var r = new Promise(function(resolve, reject){
            resolve()
          });
          r.then(() => {
            var begin = Date.now();
            while(Date.now() - begin < 1000);
            console.log("c1")
            new Promise(function(resolve, reject){
              resolve()
          }).then(() => console.log("c2"))
          });

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

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

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

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

        function sleep(duration) {
          return new Promise(function(resolve, reject) {
          console.log("b");
          setTimeout(resolve,duration);
          })
        }
        console.log("a");
        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 老师的重学前端课程,有兴趣的小伙伴也可以去了解一下。

Promise里的代码为什么比setTimeout先执行的更多相关文章

  1. 重学前端 --- Promise里的代码为什么比setTimeout先执行?

    首先通过一段代码进入讨论的主题 var r = new Promise(function(resolve, reject){ console.log("a"); resolve() ...

  2. try{}里有一个return语句,那么紧跟在这个try后的finally{}里的代码会不会被执行,什么时候被执行,在return前还是后?

    答:会执行,在方法返回调用者前执行.

  3. promise、async、await、settimeout异步原理与执行顺序

    一道经典的前端笔试题,你能一眼写出他们的执行结果吗? async function async1() { console.log("async1 start"); await as ...

  4. promise.then, setTimeout,await执行顺序问题

    promise.then VS setTimeout 在chrome和node环境环境中均输出2, 3, 1, 先输出2没什么好说的,3和1顺序让人有些意外 原因: 有一个事件循环,但是任务队列可以有 ...

  5. Promise里捕捉错误的最佳实践

    Promise里的同步部分不需要try catch new Promise((resolve, reject) => { throw new Error('error'); setTimeout ...

  6. 设置word里的代码格式,使之有底纹的效果

    目录 1    实现效果:    1 2    怎么才能在word里实现这样的显示?    1 如何设置word里的代码格式,使之有底纹的效果    2     实现效果: 怎么才能在word里实现这 ...

  7. word里的代码格式,使之有底纹的效果

      实现效果: 怎么才能在word里实现这样的显示? 如何设置word里的代码格式,使之有底纹的效果

  8. git如何merge github forked repository里的代码更新?(转)

    参考内容:git如何merge github forked repository里的代码更新? [refer to ]http://www.haojii.com/2011/08/how-to-git- ...

  9. Eclipse里的代码光标变成一个黑色块

    以前经常在编写程序是不知到碰到键盘上的那个键了,或是那几个组合键了,使得Eclipse里的代码光标变成一个黑色块:在这个状态下,光标不在活动自如,只能一直往后写代码,就不想平时的 " | & ...

随机推荐

  1. 如果用HTML5做一个在线视频聊天【原创】

    首先使用node.js 搭建一个简易的 websocket服务器: var cons = new Array(); var ws = require('ws').Server; var server ...

  2. pytorch lstm crf 代码理解

    好久没有写博客了,这一次就将最近看的pytorch 教程中的lstm+crf的一些心得与困惑记录下来. 原文 PyTorch Tutorials 参考了很多其他大神的博客,https://blog.c ...

  3. 2018-8-10-win10-uwp-自定义控件初始化

    title author date CreateTime categories win10 uwp 自定义控件初始化 lindexi 2018-08-10 19:16:50 +0800 2018-2- ...

  4. 搭建服务器上的GIT并实现自动同步到站点目录(www)

    https://blog.csdn.net/baidu_30000217/article/details/51327289 前言:当我们想要实现几个小伙伴合作开发同一个项目,或者建立一个资源分享平台的 ...

  5. CentOS 7 安装 LNMP 环境(PHP7 + MySQL5.7 + Nginx1.10)

    记录下在CentOS 7 安装 LNMP 环境(PHP7 + MySQL5.7 + Nginx1.10)过程笔记. 工具 VMware版本号 : 12.0.0 CentOS版本 : 7.0 一.修改 ...

  6. 2019-7-29-WPF-元素裁剪-Clip-属性

    title author date CreateTime categories WPF 元素裁剪 Clip 属性 lindexi 2019-7-29 10:0:13 +0800 2019-1-3 15 ...

  7. Vue 循环为选中的li列表添加效果

    <!DOCTYPE html><html><head> <meta charset="utf-8"> <title>Vu ...

  8. Linux 内核

    在接口总线领域的最新的一项是外部总线的整个类. 这包括 USB, 固件, 和 IEEE1284(基 于并口的外部总线). 这些接口有些类似于老的非外部的技术, 例如 PCMCIA/CardBus 和 ...

  9. LuoguP3066 逃跑的BarnRunning Away From…

    LuoguP3066 先吐槽一下,这道题名字好长啊 一个非常明显的思路,利用倍增数组不断向上跳.直到数值大于\(L\),然后直接差分统计答案就好了. 这种ZROI也考过,不多赘述了. 我们来考虑主席树 ...

  10. web服务器调用Servlet的过程

    Servlet程序是由WEB服务器调用,web服务器收到客户端的Servlet访问请求后: Web服务器首先检查是否已经装载并创建了该Servlet的实例对象.如果是,则直接执行第④步,否则,执行第② ...