Promise 和 setTimeout 到底谁先执行

Chrome开发工程师一篇文章
浏览器异步和单线程的问题

定时器的介绍

1.首先js是单线程,单线程的解释'单线程在程序执行时,所走的程序路径按照连续顺序排下来,前面的必须'
'处理好,后面的才会执行。'

2.setTimeout 是JavaScript 是运行于单线程的环境中的,是计划代码在未来的某个时间执行
  'setTimeout'的作用是在间隔一定的时间后,将回调函数插入消息队列中,等栈中的同步任务都执行完毕后,再执
行。因为栈中的同步任务也会耗时,所以间隔的时间一般会大于等于指定的时间。
  'setTimeout(fn, 0)'的意思是,将回调函数fn立刻插入消息队列,等待执行,而不是立即执行

3.执行时机是不能保证的,因为在页面的生命周期中, 不同时间可能有其他代码在控制 JavaScript 进程

4.在页面下载完后的'代码运行'、'事件处理程序'、'Ajax 回调函数'都必须使用同样的'线程来执行',浏览器负责
'进行排序',指派'某段代码'在'某个时间点'运行的'优先级'。

5.主 JavaScript 执行进程外,还有一个需要在进程下一次空闲时执行的代码队列。随着页面在其
生命周期中的推移,代码会按照执行顺序添加入队列。
    5.1.例如,当某个按钮被按下时,它的事件处理程序代码就会被添加到队列中,并在下一个可能的时间里
    执行。当接收到某个' Ajax 响应时','回调函数'的代码会被'添加到队列'。在 JavaScript 中没有任何代
    码是立刻执行的,但一旦进程空闲则尽快执行。

6.定时器的工作机制也是类似:'当特定时间过去后'将'代码插入'。注意,给队列添加代码并'不意味着对 它立刻执行',
而只能'表示它会尽快执行'。设定一个 150ms 后执行的定时器不代表到了 150ms 代码就立刻 执行,它表示代码
'会在 150ms 后被加入到队列中'
7.如果在这个时间点上,队列中没有其他东西,那么这 段代码就会被执行,表面上看上去好像代码就在精确指定
的时间点上执行了。其他情况下,'代码可能明显地等待更长时间才执行。'
JavaScript高级程序设计(第3版)中的一个案例来说明定时器会延迟执行
1.下面代码有一个点击事件,当点击事件结束触发,会触发里面的定时器,那么定时器真的会在事件触发的250ms
后执行么?
    答案:尽管在 255ms 处添加了定时器代码,但这时候还不能执行,因为 onclick 事件处 理程序仍在运行。
    定时器代码最早能执行的时机是在 300ms 处,即 onclick 事件处理程序结束之后。

var btn = document.getElementById("my-btn");
btn.onclick = function(){
     setTimeout(function(){
     document.getElementById("message").style.visibility = "visible";
     }, 250);
 //其他代码
}; 
小技巧连续的定时器
1.setInterval() 是连续执行的定时器,这个会有一个弊端像下面图一样:
      定时器是在 205ms 处添加到队列中的,但是直到过了 300ms 处才能够执行第一个定时器。当执行
    这个定时器代码时,在 405ms 处又给队列添加了另外一个副本。在下一个间隔,即 605ms 处,第一 
    个定时器代码仍在运行,同时在队列中已经有了一个定时器代码的实例。结果是,在这个时间点上的
    定时器代码不会被添加到队列中。结果在 5ms 处添加的定时器代码结束之后,405ms 处添加的定时器代码 
    就立刻执行。

  • 使用链式setTimeout() 解决
1.每次函数执行的时候都会创建一个新的定时器。第二个 setTimeout()调用使用了 arguments.callee 来获取对
当前执行的函数的引用,并为其设置另外一 个定时器。这样做的好处是,在前一个定时器代码执行完之前,
不会向队列插入新的定时器代码,确保 不会有任何缺失的间隔
setTimeout(function(){
     //处理中
     setTimeout(arguments.callee, interval);
}, interval);
  • 书中一个元素向右移动,当左坐标在 200 像素的时候停止的案例
setTimeout(function(){
    var div = document.getElementById("myDiv");
    left = parseInt(div.style.left) + 5;
    div.style.left = left + "px";
    if (left < 200){
        setTimeout(arguments.callee, 50);
    }
}, 50);

Promise 和 setTimeout 的执行顺序

1.Promise 和 setTimeout 都是异步,都会被在特定时间添加到队列中,那么到底谁会先执行?
2.首先要弄清'任务'和'微观任务',在'setTimeout'章节已经可以知道因为'js是单线程的缘故'所以
他需要对执行'任务'进行类似队列来决定他们的执行顺序触发时机,在任务中还有一种任务叫
'微观任务(Microtasks)'
一个说明的案例
1.下面代码打印结果为'a,b,c,d',先打印'a'在打印'b' 因为代码的先后执行顺序先依次'a' 和'b' 但是'c'
和'd' 都是异步他们的先后执行显而易见不会因为他们代码执行顺序先后而依次打印。
2.首先'Promise ':需要进行 io、等待或者其它异步操作的函数,不返回真实结果,而返回一'承诺',
函数的调用方可以在合适的时机,选择等待这个承诺兑现(通过 Promise 的 then 方法的回调)
3.这里可以简单的理解这两个异步:'Promise 属于我们说的微观任务,setTimeout 属于任务',因此
'Promise 永远在队列尾部添加微观任务。setTimeout 等宿主 API,则会添加任务。'因此可以得到
一个结论'作为微观任务的代表Promise这个异步会直接在特定情况下添加到对应队列执行尾部',
'setTimeout 则会根据调用他的宿主api执行了才把自己放到尾部'
4.因此'Promise 会比setTimeout先执行'
var r = new Promise(function(resolve, reject){
    console.log("a");
    resolve()
});
setTimeout(()=>console.log("d"), 0)
r.then(() => console.log("c"));
console.log("b")
再来一个极端的案例接着说明
1.我们延迟Promise执行时间,增加到1s,这时候打印结果如果按照我们的猜想,即使'Promise'延迟了
你'setTimeout'也要排在我的后面,因为我'Promise' 就是比你先进队列的不服你也给等着'我微观任务'
就是牛逼因此打印结果'c1 c2 d'
2.根据' 极客时间 winter老师的总结'这里要说明根据谷歌浏览器的开发博客我们可以知道没有'宏观任务的说法'
只有微观任务和任务的说法我这里理解'winter是为了让大家更好的理解将任务比喻成宏观任务':
    2.1.首先我们分析有多少个宏任务;
    2.2.在每个宏任务中,分析有多少个微任务;根据调用次序,确定宏任务中的微任务执行次序;
    2.3.根据宏任务的触发规则和调用次序,确定宏任务的执行次序;
    2.4.确定整个顺序。
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"))
});
  • 一个很有意思的属性'MutationObserver'