總結(jié)5種JavaScript異步解決方案
1.回調(diào)
回調(diào)簡(jiǎn)單地理解為一個(gè)函數(shù)作為參數(shù)傳遞給另一個(gè)函數(shù),回調(diào)是早期最常用的異步解決方案之一。
回調(diào)不一定是異步的,也不直接相關(guān)。
舉個(gè)簡(jiǎn)單的例子:
function f1(cb) { setTimeout(() => { cb && cb(); }, 2000); } f1(() => { console.log("1"); });
如上,我們?cè)诤瘮?shù)f1中使用setTimeout模擬一個(gè)耗時(shí)2s的任務(wù),在耗時(shí)任務(wù)結(jié)束時(shí)拋出回調(diào),這樣我們就可以調(diào)用它,讓回調(diào)函數(shù)在耗時(shí)結(jié)束時(shí)執(zhí)行函數(shù) f1 中的任務(wù)。
這樣,我們就把同步操作變成了異步操作。f1不會(huì)阻塞程序,相當(dāng)于先執(zhí)行程序的主要邏輯,推遲執(zhí)行耗時(shí)操作。
回調(diào)的優(yōu)點(diǎn)和缺點(diǎn)
優(yōu)點(diǎn):簡(jiǎn)單,容易理解。
缺點(diǎn):代碼不優(yōu)雅,可讀性差,不易維護(hù),耦合度高,層層嵌套造成回調(diào)地獄。
2.事件監(jiān)聽(發(fā)布訂閱模式)
發(fā)布-訂閱模式定義了對(duì)象之間一對(duì)多的依賴關(guān)系,這樣當(dāng)一個(gè)對(duì)象的狀態(tài)發(fā)生變化時(shí),所有依賴它的對(duì)象都會(huì)得到通知。
我們都使用過發(fā)布-訂閱模式,例如,如果我們將事件函數(shù)綁定到 DOM 節(jié)點(diǎn)。
document.body.addEventListener('click', function () { console.log('click'); })
但這只是發(fā)布-訂閱模式最簡(jiǎn)單的使用,在很多場(chǎng)景下我們往往會(huì)使用一些自定義事件來滿足我們的需求。
有很多方法可以實(shí)現(xiàn)發(fā)布-訂閱模式,所以這里有一個(gè)使用類的簡(jiǎn)單實(shí)現(xiàn)。
class Emitter { constructor() { // _listener array, key is the custom event name, value is the execution callback array - as there may be more than one this._listener = [] } // 訂閱 監(jiān)聽事件 on(type, fn) { // Determine if the event exists in the _listener array. // Exists to push the callback to the value array corresponding to the event name, does not exist to add directly this._listener[type] ? this._listener[type].push(fn) : (this._listener[type] = [fn]) } // Publish Trigger Event trigger(type, ...rest) { // Determine if the trigger event exists if (!this._listener[type]) return // Iterate through the array of callbacks executing the event and pass the parameters this._listener[type].forEach(callback => callback(...rest)) } }
如上所示,我們創(chuàng)建了一個(gè) Emitter 類,并在和觸發(fā)器上添加了兩個(gè)原型方法,使用如下。
// Create an emitter instance const emitter = new Emitter() emitter.on("done", function(arg1, arg2) { console.log(arg1, arg2) }) emitter.on("done", function(arg1, arg2) { console.log(arg2, arg1) }) function fn1() { console.log('I am the main program') setTimeout(() => { emitter.trigger("done", "Asynchronous parameter I", "Asynchronous parameter II") }, 1000) } fn1()
我們先創(chuàng)建一個(gè)emitter實(shí)例,然后注冊(cè)事件,然后觸發(fā)事件,這樣也解決了異步問題。
事件監(jiān)聽的優(yōu)點(diǎn)和缺點(diǎn)
優(yōu)點(diǎn):更符合模塊化思想,我們?cè)诰帉懽约旱谋O(jiān)聽器的時(shí)候可以做很多優(yōu)化,從而更好的監(jiān)聽程序的運(yùn)行。
缺點(diǎn):整個(gè)程序變成了事件驅(qū)動(dòng),或多或少影響了流程,而且每次使用都要注冊(cè)事件監(jiān)聽器然后觸發(fā),比較麻煩,代碼也不是很優(yōu)雅。
3.Promise
ES6 標(biāo)準(zhǔn)化并引入了 Promise 對(duì)象,這是一種異步編程的解決方案。
簡(jiǎn)單的說,就是用同步的方式寫異步代碼,可以用來解決回調(diào)地獄問題。
Promise對(duì)象的狀態(tài)一旦改變,就不會(huì)再改變,只有兩種可能的改變。
- 由待定改為已解決。
- 由Pending改為Rejected。
我們使用 setTimeout 來模擬異步操作。
function analogAsync(n) { return new Promise((resolve) => { setTimeout(() => resolve(n + 500), n); }); } function fn1(n) { console.log(`step1 with ${n}`); return analogAsync(n); } function fn2(n) { console.log(`step2 with ${n}`); return analogAsync(n); } function fn3(n) { console.log(`step3 with ${n}`); return analogAsync(n); }
使用 Promise 來實(shí)現(xiàn)。
function fn() { let time1 = 0; fn1(time1) .then((time2) => fn2(time2)) .then((time3) => fn3(time3)) .then((res) => { console.log(`result is ${res}`); }); } fn();
Promise 優(yōu)點(diǎn)和缺點(diǎn)
優(yōu)點(diǎn):Promise以同步的方式編寫異步代碼,避免了回調(diào)函數(shù)層層嵌套,可讀性更強(qiáng)。鏈?zhǔn)讲僮?,可以在then中繼續(xù)寫Promise對(duì)象并return,然后繼續(xù)調(diào)用then進(jìn)行回調(diào)操作。
缺點(diǎn):Promise對(duì)象一旦創(chuàng)建就會(huì)立即執(zhí)行,不能中途取消。如果沒有設(shè)置回調(diào)函數(shù),Promise 會(huì)在內(nèi)部拋出錯(cuò)誤,不會(huì)向外流。
4.Generator
Generator其實(shí)就是一個(gè)函數(shù),只不過是一個(gè)特殊的函數(shù)。Generator 的特別之處在于它可以中途停止。
function *generatorFn() { console.log("a"); yield '1'; console.log("b"); yield '2'; console.log("c"); return '3'; } let it = generatorFn(); it.next(); it.next(); it.next(); it.next();
上面的示例是一個(gè)具有以下特征的生成器函數(shù)。與普通函數(shù)不同,Generator 函數(shù)在函數(shù)之后和函數(shù)名稱之前有一個(gè) *,該函數(shù)有一個(gè)內(nèi)部 yield 字段,函數(shù)調(diào)用后的返回值使用next方法。
Generator的優(yōu)點(diǎn)和缺點(diǎn)
優(yōu)點(diǎn):優(yōu)雅的流程控制方法,允許函數(shù)被中斷地執(zhí)行。
缺點(diǎn):Generator函數(shù)的執(zhí)行必須依賴executor,對(duì)于只做異步處理還是不太方便。
5.async/await
ES2017標(biāo)準(zhǔn)引入了async函數(shù),使得異步操作更加方便。async是異步的意思,await是async wait的簡(jiǎn)寫,也就是異步等待。async/await 被許多人認(rèn)為是 js 中異步操作的終極和最優(yōu)雅的解決方案。
異步在做什么?
async 函數(shù)返回一個(gè) Promise 對(duì)象。如果直接在 async 函數(shù)中返回一個(gè)直接量,async 會(huì)通過 Promise.resolve() 將直接量包裝在一個(gè) Promise 對(duì)象中。
await 是什么?
await 是一個(gè)表達(dá)式,其計(jì)算結(jié)果為 Promise 對(duì)象或其他值(換句話說,沒有特殊限定,無論如何)。
如果 await 后面沒有跟 Promise 對(duì)象,則直接執(zhí)行。
如果 await 后面跟著一個(gè) Promise 對(duì)象,它會(huì)阻塞后面的代碼,Promise 對(duì)象解析,然后獲取 resolve 的值作為 await 表達(dá)式的結(jié)果。
await 只能在異步函數(shù)中使用
上面使用setTimeout來模擬異步操作,我們使用async/await來實(shí)現(xiàn)。
async function fn() { let time1 = 0; let time2 = await fn1(time1); let time3 = await fn2(time2); let res = await fn3(time3); console.log(`result is ${res}`); } fn();
輸出結(jié)果和上面的 Promise 實(shí)現(xiàn)是一樣的,但是 async/await 的代碼結(jié)構(gòu)看起來更清晰,幾乎和同步寫法一樣優(yōu)雅。
async/await的優(yōu)點(diǎn)和缺點(diǎn)
優(yōu)點(diǎn):內(nèi)置執(zhí)行器,語義更好,適用性更廣。
缺點(diǎn):誤用 await 可能會(huì)導(dǎo)致性能問題,因?yàn)?await 會(huì)阻塞代碼。
到此這篇關(guān)于總結(jié)5種JavaScript異步解決方案的文章就介紹到這了,更多相關(guān)解決JavaScript異步內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
JavaScript焦點(diǎn)事件、鼠標(biāo)事件和滾輪事件使用詳解
這篇文章主要介紹了JavaScript焦點(diǎn)事件、鼠標(biāo)事件和滾輪事件使用詳解,通過示例給大家講解的非常細(xì)致,有需要的小伙伴可以參考下。2016-01-01uni-app常用的幾種頁面跳轉(zhuǎn)方式總結(jié)
uni-app的頁面跳轉(zhuǎn)和小程序和vue很相似,只是方法和標(biāo)簽有所不同,這篇文章主要給大家介紹了關(guān)于uni-app常用的幾種頁面跳轉(zhuǎn)方式,文中通過實(shí)例代碼介紹的非常詳細(xì),需要的朋友可以參考下2022-08-08JS實(shí)現(xiàn)讓訪問者自助選擇網(wǎng)頁文字顏色的方法
這篇文章主要介紹了JS實(shí)現(xiàn)讓訪問者自助選擇網(wǎng)頁文字顏色的方法,涉及javascript針對(duì)radio表單控件的操作技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下2015-02-02JS高級(jí)調(diào)試技巧:捕獲和分析 JavaScript Error詳解
前端工程師都知道 JavaScript 有基本的異常處理能力。我們可以 throw new Error(),瀏覽器也會(huì)在我們調(diào)用 API 出錯(cuò)時(shí)拋出異常。但估計(jì)絕大多數(shù)前端工程師都沒考慮過收集這些異常信息2014-03-03JavaScript跳出循環(huán)的三種方法(break, return, continue)
這篇文章主要介紹了JavaScript跳出循環(huán)的三種方法(break, return, continue),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2019-07-07js省市區(qū)級(jí)聯(lián)查詢(插件版&無插件版)
這篇文章主要為大家詳細(xì)介紹了js省市區(qū)級(jí)聯(lián)查詢,包括插件版和無插件版,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-03-03JavaScript顯示當(dāng)然日期和時(shí)間即年月日星期和時(shí)間
使用js顯示當(dāng)然日期和時(shí)間在網(wǎng)頁中很是常見,方法有很多,不過多說大同小異,下面有個(gè)不錯(cuò)的示例,需要的朋友可以感受下2013-10-10JavaScript詳解使用Promise處理回調(diào)地獄的兩種方法
這篇文章主要介紹了JavaScript詳解使用Promise處理回調(diào)地獄的兩種方法,文中通過示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-11-11BootStrap Validator使用注意事項(xiàng)(必看篇)
針對(duì)bootstrap2和bootstrap3有不同的版本,在使用bootstrap validator時(shí)需要了解其注意事項(xiàng),下面小編把我遇到的注意事項(xiàng)分享給大家,供大家參考2016-09-09JS實(shí)現(xiàn)從連接中獲取youtube的key實(shí)例
這篇文章主要介紹了JS實(shí)現(xiàn)從連接中獲取youtube的key的方法,涉及javascript字符串操作的相關(guān)技巧,需要的朋友可以參考下2015-07-07