Task A --> Task B --> Task C
html
宏任務:script(全局任務),setTimeout,setInterval,setImmediate,I/O,UI rendering
微任務:process.nextTick,Promise,Object.observer,MutaionObserver
執行棧:任務在執行棧上執行。前端
分三步:node
代碼執行都是從script(全局任務)開始。web
Node.js採用V8
做爲js的解析引擎,而I/O處理方面使用了本身設計的libuv,libuv
是一個基於事件驅動的跨平臺抽象層,封裝了不一樣操做系統一些底層特性,對外提供統一的API,事件循環機制也是它裏面的實現。
每次事件循環都包含了6個階段:
1.timersajax
處理全部 setTimeout 和 setInterval 的回調。
這些回調被保存在一個最小堆(min heap) 中. 這樣引擎只須要每次判斷頭元素, 若是符合條件就拿出來執行, 直到遇到一個不符合條件或者隊列空了, 才結束 Timer Phase.
Timer Phase 中判斷某個回調是否符合條件的方法:消息循環每次進入 Timer Phase 的時候都會保存一下當時的系統時間(T1),而後只要看上述最小堆中的回調函數設置的啓動時間(T0)是否超過進入 Timer Phase 時保存的時間(T0<T1), 若是超過就拿出來執行.
Nodejs 爲了防止某個 Phase 任務太多,每一個 Phase 執行回調都有個最大數量. 若是超過數量的話也會強行結束當前 Phase 而進入下一個 Phase. 這一條規則適用於消息循環中的每個 Phase.
2. I/O callbacksegmentfault
執行你的 fs.read, socket 等 IO 操做的回調函數, 同時也包括各類 error 的回調.
3.Idle, Prepare 階段
內部使用,不討論.
4. poll 階段api
等待異步請求和數據。
首先會執行 watch_queue 隊列中的 IO 請求, 一旦 watch_queue 隊列空, 則整個消息循環就會進入 sleep , 從而等待被內核事件喚醒.
當js層代碼註冊的事件回調都沒有返回的時候,事件循環會阻塞在poll階段。看到這裏,你可能會想了,會永遠阻塞在此處嗎?固然 Poll Phase 不能一直等下去.數組
Nodejs 就是經過 Poll Phase, 對 IO 事件的等待和內核異步事件的到達來驅動整個消息循環的.
5. check 階段promise
這個階段只處理 setImmediate 的回調函數.
6. close callback 階段
專門處理一些 close 類型的回調. 好比 socket.on('close', ...). 用於資源清理.
總結:
1.Node.js 的事件循環分爲6個階段
2.瀏覽器和Node 環境下,microtask 任務隊列的執行時機不一樣
Node.js中,microtask 在事件循環的各個階段之間執行,也就是一個階段執行完畢,就會去執行microtask隊列的任務。
瀏覽器端,microtask 在事件循環的 macrotask 執行完以後執行
瀏覽器
JavaScript是一種同步的、阻塞的、單線程的語言,在這種語言中,一次只能執行一個操做。但web瀏覽器定義了函數和API,容許咱們當某些事件發生時不是按照同步方式,而是異步地調用函數(好比,時間的推移,用戶經過鼠標的交互,或者獲取網絡數據)。這意味着您的代碼能夠同時作幾件事情,而不須要中止或阻塞主線程。
前端使用異步的場景有哪些?
setTimeout()指定的延遲時間以後來執行代碼。
clearTimeout()取消setTimeout設置。
setInterval()每隔指定的時間執行代碼
clearInterval()取消setInterval()設置
用addEventListener註冊一個類型的事件的時候,瀏覽器會有一個單獨的模塊去接收這個東西,當事件被觸發的時候,瀏覽器的某個模塊,會把相應的函數扔到異步隊列中,
回調函數callback(執行結束回來調用的函數)
定義:回調函數是一個函數,它做爲參數傳遞給另外一個函數,並在父函數完成後執行。
請注意,不是全部的回調函數都是異步的,有一些是同步的。
如:Array.prototype.forEach()
forEach()
須要的參數是一個回調函數,回調函數自己帶有兩個參數,數組元素和索引值。它無需等待任何事情,當即運行。
const gods = ['Apollo', 'Artemis', 'Ares', 'Zeus']; gods.forEach(function (eachName, index){ console.log(index + '. ' + eachName); });
一般所說的回調函數是指異步的。
回調函數的問題:
通常狀況下是有異步操做時,使用Promise對這個異步操做進行封裝。
Promise
有兩種狀態改變的方式,既能夠從pending
轉變爲fulfilled
,也能夠從pending
轉變爲rejected
。一旦狀態改變,就「凝固」了,會一直保持這個狀態,不會再發生變化。當狀態發生變化,promise.then
綁定的函數就會被調用。
resolve
和reject
new Promise((resolve, reject) => { setTimeout(() => { //resolve("Hello World"); reject("error"); }, 100) }).then(data => { console.log(data); }, error => { console.log(error); });
resolve函數的做用:在異步操做成功時調用,並將異步操做的結果做爲參數傳遞出去;
reject函數的做用:在異步操做失敗時調用,並將異步操做報出的錯誤做爲參數傳遞出去。
promise對象的錯誤,會一直向後傳遞,直到被捕獲。即錯誤總會被下一個catch
所捕獲。then
方法指定的回調函數,若拋出錯誤,也會被下一個catch
捕獲。catch
中也能拋錯,則須要後面的catch
來捕獲。
then()有兩個參數,分別爲Promise從pending
變爲fulfilled
和rejected
時的回調函數(第二個參數非必選)。這兩個函數都接受Promise對象傳出的值做爲參數。
簡單來講,then
就是定義resolve
和reject
函數的,
該方法是.then(undefined, onRejected)
的別名,用於指定發生錯誤時的回調函數。
new Promise((resolve, reject) => { setTimeout(() => { //resolve("Hello World"); reject("error"); }, 100) }).then(data => { console.log(data); }, error => { console.log(error); }); //等同於 new Promise((resolve, reject) => { setTimeout(() => { //resolve("Hello World"); reject("error"); }, 100) }).then(data => { console.log(data); }).catch(error => { console.log(error); });
Promise.all(iterable)
該方法用於將多個Promise實例,包裝成一個新的Promise實例。Promise.all
方法接受一個數組(或具備(迭代器)Iterator接口)做參數,數組中的對象(p一、p二、p3)均爲promise實例(若是不是一個promise,該項會被用Promise.resolve
轉換爲一個promise)。它的狀態由這三個promise實例決定。
fulfilled
,p的狀態纔會變爲fulfilled
,並將三個promise返回的結果,按參數的順序(而不是resolved
的順序)存入數組,傳給p的回調函數,如例3.8。rejected
,p的狀態也會變爲rejected
,並把第一個被reject
的promise的返回值,傳給p的回調函數。這多個 promise 是同時開始、並行執行的,而不是順序執行。
let p1 = new Promise((resolve, reject) => { setTimeout(resolve, 3000, "first"); }); let p2 = new Promise((resolve, reject) => { setTimeout(resolve, 2000, "second"); }); let p3 = new Promise((resolve, reject) => { setTimeout(resolve, 1000, "third"); }); Promise.all([p1, p2, p3]).then(arr => { console.log(arr); }); //[ 'first', 'second', 'third' ]
let p1 = new Promise((resolve, reject) => { setTimeout(resolve, 3000, "first"); }); let p2 = new Promise((resolve, reject) => { setTimeout(reject, 2000, "second"); }); let p3 = new Promise((resolve, reject) => { setTimeout(resolve, 1000, "third"); }); Promise.all([p1, p2, p3]).then(arr => { console.log(arr); }, error => { console.log(error); }); //second
Promise.race(iterable)
Promise.race
方法一樣接受一個數組(或具備Iterator接口)做參數。當p1, p2, p3中有一個實例的狀態發生改變(變爲fulfilled
或rejected
),p的狀態就跟着改變。並把第一個改變狀態的promise的返回值,傳給p的回調函數。
let p1 = new Promise((resolve, reject) => { setTimeout(resolve, 3000, "first"); }); let p2 = new Promise((resolve, reject) => { setTimeout(reject, 2000, "second"); }); let p3 = new Promise((resolve, reject) => { setTimeout(resolve, 1000, "third"); }); Promise.race([p1, p2, p3]).then(arr => { console.log(arr); }, error => { console.log(error); }); //third
在第一個promise對象變爲resolve後,並不會取消其餘promise對象的執行
let p1 = new Promise((resolve, reject) => { setTimeout(() => { resolve("first"); console.log(1); }, 3000) }); let p2 = new Promise((resolve, reject) => { setTimeout(() => { resolve("second"); console.log(2); }, 2000) }); let p3 = new Promise((resolve, reject) => { setTimeout(() => { resolve("third"); console.log(3); }, 1000) }); Promise.race([p1, p2, p3]).then(arr => { console.log(arr); }, error => { console.log(error); }); // 3 // third // 2 // 1
Promise.resolve('Success'); /*******等同於*******/ new Promise(function (resolve) { resolve('Success'); });
Promise.reject(new Error('error')); /*******等同於*******/ new Promise(function (resolve, reject) { reject(new Error('error')); });
建立promise的流程:
new Promise(fn)
或者它的快捷方式Promise.resolve()
、Promise.reject()
,返回一個promise對象fn
中指定異步的處理 resolve
reject
情景1:若是在then中拋錯,而沒有對錯誤進行處理(即catch),那麼會一直保持reject狀態,直到catch了錯誤
function taskA() { console.log(x); console.log("Task A"); } function taskB() { console.log("Task B"); } function onRejected(error) { console.log("Catch Error: A or B", error); } function finalTask() { console.log("Final Task"); } var promise = Promise.resolve(); promise .then(taskA) .then(taskB) .catch(onRejected) .then(finalTask); -------output------- Catch Error: A or B,ReferenceError: x is not defined Final Task
Promise.resolve().then(() => { console.log('ok1'); throw 'throw error1' }).then(() => { console.log('ok2'); }, err => { // 捕獲錯誤 console.log('err->', err); }).then(() => { // 該函數將被調用 console.log('ok3'); throw 'throw error3' }).then(() => { // 錯誤捕獲前的函數不會被調用 console.log('ok4'); }).catch(err => { console.log('err->', err); }); //ok1 //err-> throw error1 //ok3 //err-> throw error3
console.log('script start'); async function async1() { console.log('async1'); await async2(); console.log('end'); } async function async2() { console.log('async2'); } setTimeout(function () { console.log('setTimeout'); }, 0); async1(); new Promise(function (resolve) { console.log('promise1'); resolve(); }).then(function () { console.log('promise2'); }); console.log('script end'); //script start //async1 //async2 //promise1 //script end //end //promise2 //setTimeout
情景二:在異步回調中拋錯,不會被catch
到
new Promise((resolve, reject) => { throw "error" }).then(data => { console.log(data); }).catch(err => { console.log(err); }); //error //對比 new Promise((resolve, reject) => { setTimeout(() => { throw "error"; }, 1000) }).then(data => { console.log(data); }).catch(err => { console.log(err); //This is never called });
優勢:
缺點:
Promise一旦運行,不能終止掉。
Promise 加載一張圖片
promise.js
function loadImg(src) { return new Promise((resolve, reject) => { let img = document.createElement("img"); img.src = src; img.onload = function (url) { resolve(img); }; img.onerror = function () { reject(err); } }); } const url1 = 'https://img.mukewang.com/5a9fc8070001a82402060220-140-140.jpg'; const url2 = 'https://img3.mukewang.com/5a9fc8070001a82402060220-100-100.jpg'; loadImg(url1).then((img) => { console.log("圖片1加載成功"); console.log(`圖片1的寬度爲:${img.width}`); }).then(() => { console.log("準備加載圖片2"); return loadImg(url2); }).then((img) => { console.log("圖片2加載成功"); console.log(`圖片2的寬度爲:${img.width}`); }).catch((err) => { console.log(err); });
index.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> </body> <script src="./promise.js"></script> </html>
Generator解決了回調函數處理異步流程的第一個問題:不符合大腦順序、線性的思惟方式。
// 使用setTimeout模擬異步 function ajax (url){ return new Promise(function(resolve, reject){ setTimeout(function(){ console.log(url + ' result.'); resolve(url + ' result.'); }, 100); }); } async function ajaxAsync () { var aResult = await ajax('/api/a'); console.log('aResult: ' + aResult); var bResult = await ajax('/api/b'); console.log('bResult: ' + bResult); } ajaxAsync();
參考文章:
http://www.javashuo.com/article/p-odwhqqel-bp.html
http://www.javashuo.com/article/p-ogqwcufq-go.html