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函数语句块进行处理,预防可能出现的异常。