js 關於setTimeout和Promise執行順序問題
異步 -- Promise和setTimeout 執行順序
Promise 和 setTimeout 到底誰先執行
定時器的介紹
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'