JavaScript是一門單線程的編程語言,因此沒有真正意義上的並行特性。web
爲了協調事件處理、頁面交互、腳本調用、UI渲染、網絡請求等行爲對主線程形成的影響,事件循環(event loop)方案應運而生。編程
事件循環說白了就是一個不斷的在等待任務、執行任務的方案。後端
在JavaScript中,根據執行方式的不一樣,有2種狀態的任務,分別是同步任務和異步任務。數組
同步任務率先執行,然後執行異步任務,全部的異步任務由2個隊列存儲,分別是:promise
主線程在執行完同步任務後,會不斷的從這2個任務隊列中按照先進先出的策略取出異步任務並執行。服務器
而且在此期間也會有新的事件不斷的加入至各個任務隊列中,以此循環往復、永不阻塞。網絡
以下圖所示:dom
宏任務包括:異步
微任務包括:async
根據任務的狀態,任務的執行優先級也會有所不一樣,具體執行順序以下所示:
而關於微任務和宏任務的執行,還有更詳細的劃分:
以下圖所示:
代碼測試:
"use strict"; // 宏任務,每5s添加一個微任務並執行 setInterval(() => { async function foo() { return "micro-task" } async function bar() { let result = await foo(); console.log(result); } bar(); }, 5000); // 宏任務,每1s執行一次 setInterval(() => { console.log("macro-task"); }, 1000); // 同步任務 (() => { console.log("hello world"); })();
測試結果,雖然同步任務的代碼在最下面,可是它會最早執行,而每添加一個微任務時,宏任務的執行會被插隊:
Promise是ES6中出現的新功能,用於在JavaScript中更加簡單的實現異步編程。
咱們可使用new Promise()建立出一個Promise對象,它接收一個執行器函數,該函數須要指定resolve和reject參數用於改變當前Promise對象的執行狀態。
因爲Promise對象中執行器代碼是屬於同步任務,因此他會率先的進行執行,一個Promise對象擁有如下幾種狀態:
注意,每一個Promise對象的狀態只容許改變一次!不能夠屢次更改。
示例以下。
1)Promise中執行器任務是同步任務,因此會率先執行:
"use strict"; setInterval(() => { console.log("macro task 3"); }, 1000) let task = new Promise((resolve, reject) => { console.log("sync task 1"); }); console.log("sync task 2"); // sync task 1 // sync task 2 // macro task 3
2)使用resolve改變Promise對象的狀態爲fulfilled:
"use strict"; let task = new Promise((resolve, reject) => { let x = Math.floor(Math.random() * 100) + 1; let y = Math.floor(Math.random() * 100) + 1; let result = x + y; // 返回結果爲resolve()中的值 resolve(result); }); console.log(task); // Promise {<fulfilled>: 83}
3)使用reject改變Promise對象的狀態爲rejected, 它將引起一個異常:
"use strict"; let task = new Promise((resolve, reject) => { let x = Math.floor(Math.random() * 100) + 1; let y = Math.floor(Math.random() * 100) + 1; let result = x + y; // 返回結果爲reject()中的值 reject("error!") }); console.log(task); // Promise {<rejected>: "error!"} // Uncaught (in promise) error!
4)若是未使用resolve或reject改變Promise對象狀態,那麼該任務的狀態將爲pending:
"use strict"; let task = new Promise((resolve, reject) => { let x = Math.floor(Math.random() * 100) + 1; let y = Math.floor(Math.random() * 100) + 1; let result = x + y; }); console.log(task); // Promise {<pending>}
咱們能夠在Promise對象後,添加一個用於處理任務狀態的回調then()方法。
then()方法只有在Promise對象狀態爲fulfilled或者rejected時纔會進行執行,它具備2個參數,接收2個回調函數:
此外,then()方法是屬於微任務,因此他會插在宏任務以前進行執行。
代碼示例以下:
1)Promise對象狀態爲fulfilled,運行then()方法的第1個回調函數:
"use strict"; let task = new Promise((resolve, reject) => { resolve("success"); }).then( value => { console.log(value); }, reason => { console.log(reason); }); // success
2)Promise對象狀態爲rejected,運行then()方法的第2個回調函數:
"use strict"; let task = new Promise((resolve, reject) => { throw new Error("error"); }).then( value => { console.log(value); }, reason => { console.log(reason); }); // error
其實每個then()都將返回一個全新的Promise,默認狀況下,該Promise的狀態是fulfilled。
此時就會產生一種鏈式關係,每個then()都將返回一個新的Promise對象,而每一個then()的做用又都是處理上個Promise對象的狀態。
這意味着咱們能夠無限的鏈式排列then(),以下所示:
代碼示例:
"use strict"; let task = new Promise((resolve, reject) => { console.log("first Promise task status is fulfilled"); resolve("success"); }) .then( value => { console.log("then1 fulfilled", value); return value }, reason => { console.log(reason); }) .then( value => { console.log("then2 fulfilled", value); return value }, reason => { console.log(reason); }) .then( value => { console.log("then3 fulfilled", value); return value }, reason => { console.log(reason); }); // first Promise task status is fulfilled // then1 fulfilled success // then2 fulfilled success // then3 fulfilled success
要想真正的瞭解鏈式調用,就必須搞明白每一個then()在不一樣狀態下的返回值對下一個then()的影響。
具體狀況以下所示:
1)當前then()無返回值,則當前Promise狀態則爲fulfilled。
下一個then()的onfulfilled回調函數參數value爲undefined:
代碼示例:
"use strict"; let task = new Promise((resolve, reject) => { console.log("first Promise task status is fulfilled"); resolve("success"); }) .then( value => { console.log("then1 fulfilled", value); // success }, reason => { console.log(reason); }) .then( value => { console.log("then2 fulfilled", value); // undefined }, reason => { console.log(reason); }); // first Promise task status is fulfilled // then1 fulfilled success // then2 fulfilled undefined
2)當前then()有返回值,則當前Promise狀態則爲fulfilled。
下一個then()的onfulfilled回調函數參數value爲當前then()的返回值:
代碼示例:
let task = new Promise((resolve, reject) => { console.log("first Promise task status is fulfilled"); resolve("success"); }) .then( value => { console.log("then1 fulfilled", value); // success return "then1 value" }, reason => { console.log(reason); }) .then( value => { console.log("then2 fulfilled", value); // then1 value }, reason => { console.log(reason); }); // first Promise task status is fulfilled // then1 fulfilled success // then2 fulfilled then1 value
3)當前then()有返回值,且返回了一個狀態爲fulfilled的Promise對象。
下一個then()的onfulfilled回調函數參數value爲當前then()中被返回Promise裏resolve()所傳遞的值:
代碼示例:
"use strict"; let task = new Promise((resolve, reject) => { console.log("first Promise task status is fulfilled"); resolve("success"); }) .then( value => { console.log("then1 fulfilled", value); // success return new Promise((resolve, reject) => { resolve("then1 Promise success") }) }, reason => { console.log(reason); }) .then( value => { console.log("then2 fulfilled", value); // then1 Promise success }, reason => { console.log(reason); }); // first Promise task status is fulfilled // then1 fulfilled success // then2 fulfilled then1 Promise success
4)當前then()有返回值,且返回了一個狀態爲rejected的Promise對象。
下一個then()的onrejected回調函數參數reason爲當前then()中被返回Promise裏reject()所傳遞的值,或者是被返回Promise裏拋出異常的值:
代碼示例:
"use strict"; let task = new Promise((resolve, reject) => { console.log("first Promise task status is fulfilled"); resolve("success"); }) .then( value => { console.log("then1 fulfilled", value); // success return new Promise((resolve, reject) => { reject("then1 Promise error") }) }, reason => { console.log(reason); }) .then( value => { console.log("then2 fulfilled", value); }, reason => { console.log(reason); // then1 Promise error }); // first Promise task status is fulfilled // then1 fulfilled success // then1 Promise error
5)當前then()有返回值,且返回了一個狀態爲pending的Promise對象。下一個then()則必須等待當前then()中被返回Promise對象狀態發生改變後才能繼續執行:
代碼示例:
"use strict"; let task = new Promise((resolve, reject) => { console.log("first Promise task status is fulfilled"); resolve("success"); }) .then( value => { console.log("then1 fulfilled", value); // success return new Promise((resolve, reject) => { console.log("pending"); }) }, reason => { console.log(reason); }) .then( value => { console.log("then2 fulfilled", value); }, reason => { console.log(reason); }); // first Promise task status is fulfilled // then1 fulfilled success // pending
另外,若是在代碼執行時拋出了異常,那麼返回的Promise對象狀態則爲rejected,下一個then()的onrejected回調函數參數reason爲當前then()中拋出異常的值,這裏再也不進行演示。
then()是具備穿透功能的,當一個then()沒有指定須要被執行的回調函數時,它將繼續冒泡向下傳遞:
代碼示例:
"use strict"; let task = new Promise((resolve, reject) => { console.log("first Promise task status is fulfilled"); resolve("success"); }) .then() .then( value => { console.log("then2 fulfilled", value); }, reason => { console.log(reason); }); // first Promise task status is fulfilled // then2 fulfilled success
每一個then()均可以指定onrejected回調函數用於處理上一個Promise狀態爲rejected的狀況。若是每一個then()都進行這樣的設置會顯得很麻煩,因此咱們只須要使用catch()便可。
catch()能夠捕獲以前全部Promise的錯誤執行,故建議將catch()放在最後。
catch()須要指定一個回調函數onrejected,具備1個參數reason,接收Promise任務中reject()或異常發生時所傳遞的值。
錯誤是冒泡傳遞的,若是沒有任何一個then()定義onrejected的回調函數,那麼錯誤將一直冒泡到catch()處進行處理:
代碼示例:
"use strict"; let task = new Promise((resolve, reject) => { console.log("first Promise task status is fulfilled"); resolve("success"); }) .then( value => { console.log("then1 fulfilled", value); return value } ) .then( value => { console.log("then2 rejected", value); throw new Error("error"); }) .then( value => { console.log("then3 ...", value); } ) .catch( reason => { console.log(reason); }); // first Promise task status is fulfilled // then1 fulfilled success // then2 rejected success // Error: error
finally()是不管任務處理成功或者失敗都會執行,所以建議將它放在鏈式調用的最後面。
它須要指定一個回調函數onfinally,該回調函數沒有任何參數:
"use strict"; let task = new Promise((resolve, reject) => { console.log("first Promise task status is fulfilled"); resolve("success"); }) .then( value => { console.log("then1 fulfilled", value); return value } ) .catch( reason => { console.log(reason); } ) .finally( () => { console.log("run"); } ); // first Promise task status is fulfilled // then1 fulfilled success // run
resolve()方法用於快速返回一個狀態爲fulfilled的Promise對象,生產環境中使用較少:
"use strict"; let task = Promise.resolve("success"); console.log(task); // Promise {<fulfilled>: "success"}
reject()方法用於快速返回一個狀態爲rejected的Promise對象,生產環境中使用較少:
"use strict"; let task = Promise.reject("error"); console.log(task); // Promise {<rejected>: "error"} // Uncaught (in promise) error
all()方法用於一次同時執行多個異步任務,而且必須確保這些任務是成功的。
all()方法應用場景仍是很是普遍的,如咱們須要使用Ajax請求後端的書籍與價格信息時,不管是書籍獲取失敗仍是價格獲取失敗,都將認爲這次任務的失敗。
示例以下:
"use strict"; const getBookNameTask = new Promise((resolve, reject) => { // 模擬請求後端的書籍名稱,須要花費3s setTimeout(() => { resolve(JSON.stringify( ["HTML", "CSS", "JavaScript"] )) }, 3000); }); const getBookPriceTask = new Promise((resolve, reject) => { // 模擬請求後端的書籍價格,須要花費5s setTimeout(() => { resolve(JSON.stringify( [98, 120, 40] )) }, 5000); }) // 執行任務 Promise.all( [getBookNameTask, getBookPriceTask] ) .then(value => { // 書籍和價格所有獲取後才執行這裏 // value = ["[\"HTML\",\"CSS\",\"JavaScript\"]", "[98,120,40]"] const bookNameArray = JSON.parse(value[0]); const bookPriceArray = JSON.parse(value[1]); const bookAndNameMap = new Map(); for (let i = 0; i < bookNameArray.length; i++) { bookAndNameMap.set(bookNameArray[i], bookPriceArray[i]); } console.log(bookAndNameMap); }) .catch(reason => { // 任何一個沒獲取到都執行這裏 console.log(reason); });
allSettled()方法和all()方法類似,都是用於同時執行多個異步任務,可是它並不關心全部任務是否都執行成功。
allSettled()的狀態只會是fulfilled:
"use strict"; const getBookNameTask = new Promise((resolve, reject) => { setTimeout(() => { reject("error! Can't query all books name") }, 3000); }); const getBookPriceTask = new Promise((resolve, reject) => { setTimeout(() => { reject("error! Can't query all books price") }, 5000); }) // 執行任務 Promise.allSettled( [getBookNameTask, getBookPriceTask] ) .then(value => { // 無論怎樣都會執行這裏 console.log("run me"); })
race()也可同時執行多個任務,它僅會返回最快完成任務的執行結果。
race()方法用的也比較多,如咱們須要加載一些圖片,這些圖片在多個服務端上都有存儲,但爲了提升用戶體驗咱們須要根據用戶所在的地理位置選擇最近的服務器,此時race()就派上了用場:
"use strict"; const getCacheImages = new Promise((resolve, reject) => { setTimeout(() => { resolve("get cache images success!!"); }, 1000); }) const getWebImages = new Promise((resolve, reject) => { setTimeout(() => { resolve("get web images success!!"); }, 3000); }) // 建立任務 Promise.race( [getCacheImages, getWebImages] ) .then(value => { console.log(value); }) .catch(reason => { console.log(reason); }) // get cache images success!!
async實際上是new Promise()的語法糖簡寫形式。
在某一個函數前面加上async,運行該函數時將會返回一個Promise對象。
示例演示:
"use strict"; async function task() { return "success" } task().then(value => { console.log(value); }); // success
await實際上是then()的另外一種寫法,它只能在async函數中使用。
以下所示,咱們有3個任務,這3個任務必須是先經過用戶ID獲取人員姓名、再經過用戶ID獲取信息ID、最後再經過用戶ID獲取人員信息。
若是你用純Promise+then()的方式進行代碼編寫,它將是這樣的:
"use strict"; const idAndName = new Map([ [1, "Jack"], [2, "Tom"], [3, "Mary"], ]); const personnelInformation = new Map([ [1, { gender: "female", age: 18, addr: "TianJin", desc: "my name is Mary" }], [2, { gender: "male", age: 21, addr: "ShangHai", desc: "my name is Tom" }], [3, { gender: "male", age: 18, addr: "BeiJing", desc: "my name is Jack" }], ]); const nameAndMessage = new Map([ [1, 3], [2, 2], [3, 1], ]) function getUserMessage(id) { let userName, messageId, message, str; new Promise((resolve, reject) => { // 獲取姓名 if (idAndName.has(id)) { userName = idAndName.get(id); resolve(); } reject(`no information id : ${id}`); }) .then(() => { // 獲取關係 messageId = nameAndMessage.get(id); }) .then(() => { // 獲取信息 message = personnelInformation.get(messageId); }) .then(() => { // 進行渲染 str = `name : ${userName}</br>`; for (let [k, v] of Object.entries(message)) { str += `${k} : ${v}</br>`; } document.write(str) }) .catch(reason => { document.write(`<p style="color:red">${reason}</p>`); }) } getUserMessage(3);
若是你使用async+awit的方式編寫,那麼它的邏輯就會清楚不少:
"use strict"; const idAndName = new Map([ [1, "Jack"], [2, "Tom"], [3, "Mary"], ]); const personnelInformation = new Map([ [1, { gender: "female", age: 18, addr: "TianJin", desc: "my name is Mary" }], [2, { gender: "male", age: 21, addr: "ShangHai", desc: "my name is Tom" }], [3, { gender: "male", age: 18, addr: "BeiJing", desc: "my name is Jack" }], ]); const nameAndMessage = new Map([ [1, 3], [2, 2], [3, 1], ]) // 獲取姓名 async function getName(id) { if (idAndName.has(id)) { return idAndName.get(id); } throw new Error(`no information id : ${id}`); } // 獲取關係 async function getRelation(id) { return nameAndMessage.get(id); } // 獲取信息 async function getMessage(messageId) { return personnelInformation.get(messageId); } // 入口函數,進行渲染 async function getUserMessage(id) { try { let userName = await getName(id); // 必須等待該函數執行完成纔會繼續向下執行 let messageId = await getRelation(id); let message = await getMessage(messageId); let str = `name : ${userName}</br>`; for (let [k, v] of Object.entries(message)) { str += `${k} : ${v}</br>`; } document.write(str) } catch (e) { document.write(`<p style="color:red">${e}</p>`); } } getUserMessage(3);
async+await的異常處理推薦使用try+catch語句將全部執行代碼進行包裹,它將處理全部可能出現的異常,至關於在鏈式調用的最後面加上catch()方法:
"use strict"; async function task01() { console.log("run task 01"); } async function task02() { throw new Error("task02 error"); console.log("run task 02"); } async function task03() { console.log("run task 03"); } async function main() { try { await task01(); await task02(); await task03(); } catch (e) { console.log(e); } } main();
也能夠在主函數外部使用catch()方法來處理異常,可是我並不推薦這麼作。
"use strict"; async function task01() { console.log("run task 01"); } async function task02() { throw new Error("task02 error"); console.log("run task 02"); } async function task03() { console.log("run task 03"); } async function main() { await task01(); await task02(); await task03(); } main().catch(reason => { console.log(reason); });
除此以外,你也可使用try+catch語句塊對單獨的async函數語句塊進行處理,預防可能出現的異常。