總結(jié)JavaScript處理異步的方法
javascript語(yǔ)言的執(zhí)行環(huán)境是單線程(single thread),就是指一次只能完成一件任務(wù)。如果有多個(gè)任務(wù),就必須排隊(duì),前面一個(gè)任務(wù)完成,再執(zhí)行后面一個(gè)任務(wù),以此類推。
這種模式的好處是實(shí)現(xiàn)起來(lái)比較簡(jiǎn)單,執(zhí)行環(huán)境相對(duì)單純;但是只要耗時(shí)比較多,假如有一個(gè)任務(wù)耗時(shí)很長(zhǎng),后面的任務(wù)都必須排隊(duì)等著,會(huì)拖延整個(gè)程序的執(zhí)行。為了解決這個(gè)問(wèn)題,Javascript語(yǔ)言將任務(wù)的執(zhí)行模式分成兩種:同步(Synchronous)和異步(Asynchronous)。
- 同步模式:就是一個(gè)任務(wù)先執(zhí)行,后一個(gè)任務(wù)等待前一個(gè)任務(wù)結(jié)束,然后再執(zhí)行,程序的執(zhí)行順序與任務(wù)的排列順序是一致的、同步的;
- 異步模式::每一個(gè)任務(wù)有一個(gè)或多個(gè)回調(diào)函數(shù)(callback),前一個(gè)任務(wù)結(jié)束后,不是執(zhí)行后一個(gè)任務(wù),而是執(zhí)行回調(diào)函數(shù),后一個(gè)任務(wù)則是不等前一個(gè)任務(wù)結(jié)束就執(zhí)行,所以程序的執(zhí)行順序與任務(wù)的排列順序是不一致的、異步的。
Javascript處理異步的方法有以下幾種:
一、回調(diào)函數(shù)
回調(diào)是一個(gè)函數(shù)被作為一個(gè)參數(shù)傳遞到另一個(gè)函數(shù)里,在那個(gè)函數(shù)執(zhí)行完后再執(zhí)行?;卣{(diào)函數(shù)是異步編程最基本的方法,其優(yōu)點(diǎn)是簡(jiǎn)單、容易理解和部署;缺點(diǎn)是容易產(chǎn)生回調(diào)地獄。
- ajax('XXX1', () => {
- // callback 函數(shù)體
- ajax('XXX2', () => {
- // callback 函數(shù)體
- ajax('XXX3', () => {
- // callback 函數(shù)體
- })
- })
- })
這就是所謂的回調(diào)地獄,回調(diào)地獄帶來(lái)的負(fù)面作用有以下幾點(diǎn):
- 代碼臃腫,可讀性差,可維護(hù)性差。
- 代碼復(fù)用性差。
- 容易滋生 bug。
- 只能在回調(diào)里處理異常。
二、事件監(jiān)聽(tīng)
這種方式,異步任務(wù)的執(zhí)行不取決于代碼的順序,而取決于某個(gè)事件是否發(fā)生。
(1) 普通方式
- f1.on('done', f2);
上面這行代碼的意思是,當(dāng)f1發(fā)生done事件,就執(zhí)行f2。
(2) onclick方法
- element.onclick=function(){
- //處理函數(shù)
- }
- element.onclick=handler1;
- element.onclick=handler2;
- element.onclick=handler3;
- // 只有handler3會(huì)被添加執(zhí)行
優(yōu)點(diǎn):寫(xiě)法兼容到主流瀏覽器;
缺點(diǎn):當(dāng)同一個(gè)element元素綁定多個(gè)事件時(shí),只有最后一個(gè)事件會(huì)被添加
(3) addEvenListener
- elment.addEvenListener("click",handler1,false);
- elment.addEvenListener("click",handler2,false);
- elment.addEvenListener("click",handler3,false);
該方法的第三個(gè)參數(shù)是一個(gè)布爾值:當(dāng)為false時(shí)表示由里向外,true表示由外向里。
三、發(fā)布/訂閱模式
我們假定,存在一個(gè)"信號(hào)中心",某個(gè)任務(wù)執(zhí)行完成,就向信號(hào)中心"發(fā)布"(publish)一個(gè)信號(hào),其他任務(wù)可以向信號(hào)中心"訂閱"(subscribe)這個(gè)信號(hào),從而知道什么時(shí)候自己可以開(kāi)始執(zhí)行。這就叫做"發(fā)布/訂閱模式"(publish-subscribe pattern)
首先,f2向信號(hào)中心jQuery訂閱done信號(hào)。
- jQuery.subscribe('done', f2);
然后,f1進(jìn)行如下改寫(xiě):
- function f1() {
- setTimeout(function () {
- jQuery.publish('done');
- }, 1000);
- }
f1執(zhí)行完成后,向信號(hào)中心jQuery發(fā)布done信號(hào),從而引發(fā)f2的執(zhí)行。f2完成執(zhí)行后,可以取消訂閱(unsubscribe)
- jQuery.unsubscribe('done', f2);
這種方式的優(yōu)點(diǎn):可以通過(guò)查看“消息中心”,了解存在多少信號(hào)、每個(gè)信號(hào)有多少訂閱者,從而監(jiān)控程序的運(yùn)行。
四、promise
以上都是ES6之前的異步處理方式。ES6之后出現(xiàn)了promise。它是異步編程的一種解決方案,比傳統(tǒng)的解決方案(回調(diào)函數(shù))——更合理和更強(qiáng)大。
Promise 對(duì)象有以下兩個(gè)特點(diǎn)。
- 對(duì)象的狀態(tài)不受外界影響。Promise 對(duì)象代表一個(gè)異步操作,有三種狀態(tài):pending(進(jìn)行中)、fulfilled(已成功)和rejected(已失敗)。只有異步操作的結(jié)果,可以決定當(dāng)前是哪一種狀態(tài),任何其他操作都無(wú)法改變這個(gè)狀態(tài)。
- 一旦狀態(tài)改變,就不會(huì)再變,任何時(shí)候都可以得到這個(gè)結(jié)果
1. 基本用法
(1) ES6 規(guī)定,Promise 對(duì)象是一個(gè)構(gòu)造函數(shù),用來(lái)生成 Promise 實(shí)例。
- const promise = new Promise((resolve, reject) => {
- if (/* 異步操作成功 */){
- resolve(success)
- } else {
- reject(error)
- }
- })
Promise接收一個(gè)函數(shù)作為參數(shù),函數(shù)里有resolve和reject兩個(gè)參數(shù):
- resolve方法的作用是將Promise的pending狀態(tài)變?yōu)閒ullfilled,在異步操作成功之后調(diào)用,可以將異步返回的結(jié)果作為參數(shù)傳遞出去。
- reject方法的作用是將Promise的pending狀態(tài)變?yōu)閞ejected,在異步操作失敗之后調(diào)用,可以將異步返回的結(jié)果作為參數(shù)傳遞出去。
- 他們之間只能有一個(gè)被執(zhí)行,不會(huì)同時(shí)被執(zhí)行,因?yàn)镻romise只能保持一種狀態(tài)。
(2) Promise 實(shí)例生成以后,可以用then方法分別指定resolved狀態(tài)和rejected狀態(tài)的回調(diào)函數(shù)。
- promise.then((success) => {
- // 對(duì)應(yīng)于上面的resolve(success)方法
- }, (error) => {
- // 對(duì)應(yīng)于上面的reject(error)方法
- }
- // 還可以寫(xiě)成這樣 (推薦使用這種寫(xiě)法)
- promise.then((success) => {
- // 對(duì)應(yīng)于上面的resolve(success)方法
- }).catch((error) => {
- // 對(duì)應(yīng)于上面的reject(error)方法
- })
then(onfulfilled,onrejected)方法中有兩個(gè)參數(shù),兩個(gè)參數(shù)都是函數(shù):
- 第一個(gè)參數(shù)執(zhí)行的是resolve()方法(即異步成功后的回調(diào)方法)
- 第二參數(shù)執(zhí)行的是reject()方法(即異步失敗后的回調(diào)方法)(第二個(gè)參數(shù)可選)。
- 它返回的是一個(gè)新的Promise對(duì)象。
(3) promise構(gòu)造函數(shù)是同步執(zhí)行的,then方法是異步執(zhí)行的
- const promise = new Promise((resolve, reject) => {
- console.log(1)
- resolve()
- console.log(2)
- })
- promise.then(() => {
- console.log(3)
- })
- console.log(4)
- // 1 2 4 3
2. Promise.finally()
Promise.finally()用于指定不管 Promise 對(duì)象最后狀態(tài)如何,都會(huì)執(zhí)行的操作。
- promise
- .then(result => {···})
- .catch(error => {···})
- .finally(() => {···});
3. Promise.all()
Promise.all()用于處理多個(gè)異步處理,比如說(shuō)一個(gè)頁(yè)面上需要等多個(gè) ajax 的數(shù)據(jù)回來(lái)才執(zhí)行相關(guān)邏輯。
- const p = Promise.all([p1, p2, p3]);
p的狀態(tài)由p1、p2、p3決定,分成兩種情況。
- 只有p1、p2、p3的狀態(tài)都變成fulfilled,p的狀態(tài)才會(huì)變成fulfilled,此時(shí)p1、p2、p3的返回值組成一個(gè)數(shù)組,傳遞給p的回調(diào)函數(shù)。
- 只要p1、p2、p3之中有一個(gè)被rejected,p的狀態(tài)就變成rejected,此時(shí)第一個(gè)被reject的實(shí)例的返回值,會(huì)傳遞給p的回調(diào)函數(shù)。
4. Promse.race()
Promse.race()就是賽跑的意思,Promise.race([p1, p2, p3])里面哪個(gè)結(jié)果獲得的快,就返回那個(gè)結(jié)果,不管結(jié)果本身是成功狀態(tài)還是失敗狀態(tài)。
- const p = Promise.race([p1, p2, p3])
上面代碼中,只要p1、p2、p3之中有一個(gè)實(shí)例率先改變狀態(tài),p的狀態(tài)就跟著改變。那個(gè)率先改變的 Promise 實(shí)例的返回值,就傳遞給p的回調(diào)函數(shù)。
五、async/await
async/await是JavaScript為了解決異步問(wèn)題而提出的一種解決方案,許多人將其稱為異步的終極解決方案。async 函數(shù),就是 Generator 函數(shù)的語(yǔ)法糖。
相較于 Generator,Async 函數(shù)的改進(jìn)在于下面四點(diǎn):
- 內(nèi)置執(zhí)行器。Generator 函數(shù)的執(zhí)行必須依靠執(zhí)行器,而 Aysnc 函數(shù)自帶執(zhí)行器,調(diào)用方式跟普通函數(shù)的調(diào)用一樣。
- 更好的語(yǔ)義。async 和 await 相較于 * 和 yield 更加語(yǔ)義化。
- 更廣的適用性。co 模塊約定,yield 命令后面只能是 Thunk 函數(shù)或 Promise對(duì)象。而 async 函數(shù)的 await 命令后面可以是 Promise 或者原始類型(Number,string,boolean,但這時(shí)等同于同步)。
- 返回值是 Promise。async 函數(shù)返回值是 Promise 對(duì)象,比 Generator 函數(shù)返回的 Iterator 對(duì)象方便,可以直接使用 then() 方法進(jìn)行調(diào)用。
1. 使用規(guī)則
(1) 凡是在前面添加了async的函數(shù)在執(zhí)行后都會(huì)自動(dòng)返回一個(gè)Promise對(duì)象
- async function test() {
- }
- let result = test()
- console.log(result) //即便代碼里test函數(shù)什么都沒(méi)返回,我們依然打出了Promise對(duì)象
(2) await必須在async函數(shù)里使用,不能單獨(dú)使用
- function test() {
- let result = await Promise.resolve('success')
- console.log(result)
- }
- test() //執(zhí)行以后會(huì)報(bào)錯(cuò)
2. await 在等什么
- 如果await等到的不是一個(gè)promise對(duì)象,那跟著的表達(dá)式的運(yùn)算結(jié)果就是它等到的東西;
- 如果是一個(gè)promise對(duì)象,await會(huì)阻塞后面的代碼,等promise對(duì)象resolve,得到resolve的值作為await表達(dá)式的運(yùn)算結(jié)果
- 雖然await阻塞了,但await在async中,async不會(huì)阻塞,它內(nèi)部所有的阻塞都被封裝在一個(gè)promise對(duì)象中異步執(zhí)行