一篇文章詳解JS的四種異步解決方案
前言
「異步編程」是前端工程師日常開發(fā)中經(jīng)常會(huì)用到的技術(shù),也是校招面試過(guò)程中常考的一個(gè)知識(shí)點(diǎn)。
通過(guò)掌握「異步編程」的四種方式,可以讓我們能夠更好地處理JavaScript中的異步操作,提高代碼的性能和用戶體驗(yàn)。
因此,「今天就想和大家來(lái)聊聊JS異步編程的四種方式!」
同步&異步的概念
在講這四種異步方案之前,我們先來(lái)明確一下同步和異步的概念:
所謂「同步(synchronization)」,簡(jiǎn)單來(lái)說(shuō),就是「順序執(zhí)行」,指的是同一時(shí)間只能做一件事情,只有目前正在執(zhí)行的事情做完之后,才能做下一件事情。
「同步操作的優(yōu)點(diǎn)」在于做任何事情都是依次執(zhí)行,井然有序,不會(huì)存在大家同時(shí)搶一個(gè)資源的問(wèn)題。
「同步操作的缺點(diǎn)」在于「會(huì)阻塞后續(xù)代碼的執(zhí)行」。如果當(dāng)前執(zhí)行的任務(wù)需要花費(fèi)很長(zhǎng)的時(shí)間,那么后面的程序就只能一直等待。
所謂「異步(Asynchronization)」,指的是當(dāng)前代碼的執(zhí)行不影響后面代碼的執(zhí)行。當(dāng)程序運(yùn)行到異步的代碼時(shí),會(huì)將該異步的代碼作為任務(wù)放進(jìn)「任務(wù)隊(duì)列」,而不是推入主線程的調(diào)用棧。等主線程執(zhí)行完之后,再去任務(wù)隊(duì)列里執(zhí)行對(duì)應(yīng)的任務(wù)即可。
因此,「異步操作的優(yōu)點(diǎn)就是:不會(huì)阻塞后續(xù)代碼的執(zhí)行?!?/strong>
js中異步的應(yīng)用場(chǎng)景
開篇講了同步和異步的概念,那么在JS中異步的應(yīng)用場(chǎng)景有哪些呢?
「定時(shí)任務(wù)」:setTimeout、setInterval
「網(wǎng)絡(luò)請(qǐng)求」:ajax請(qǐng)求、動(dòng)態(tài)創(chuàng)建img標(biāo)簽的加載
「事件監(jiān)聽器」:addEventListener
實(shí)現(xiàn)異步的四種方法
對(duì)于「setTimeout、setInterval、addEventListener」這種異步場(chǎng)景,不需要我們手動(dòng)實(shí)現(xiàn)異步,直接調(diào)用即可。
但是對(duì)于「ajax請(qǐng)求」、「node.js中操作數(shù)據(jù)庫(kù)這種異步」,就需要我們自己來(lái)實(shí)現(xiàn)了~
1、 回調(diào)函數(shù)
在微任務(wù)隊(duì)列出現(xiàn)之前,JS實(shí)現(xiàn)異步的主要方式就是通過(guò)「回調(diào)函數(shù)」。
以一個(gè)簡(jiǎn)易版的Ajax請(qǐng)求為例,代碼結(jié)構(gòu)如下所示:
function ajax(obj){ let default = { url: '...', type:'GET', async:true, contentType: 'application/json', success:function(){} }; for (let key in obj) { defaultParam[key] = obj[key]; } let xhr; if (window.XMLHttpRequest) { xhr = new XMLHttpRequest(); } else { xhr = new ActiveXObject('Microsoft.XMLHTTP'); } xhr.open(defaultParam.type, defaultParam.url+'?'+dataStr, defaultParam.async); xhr.send(); xhr.onreadystatechange = function (){ if (xhr.readyState === 4){ if(xhr.status === 200){ let result = JSON.parse(xhr.responseText); // 在此處調(diào)用回調(diào)函數(shù) defaultParam.success(result); } } } }
我們?cè)跇I(yè)務(wù)代碼里可以這樣調(diào)用「ajax請(qǐng)求」:
ajax({ url:'#', type:GET, success:function(e){ // 回調(diào)函數(shù)里就是對(duì)請(qǐng)求結(jié)果的處理 } });
「ajax請(qǐng)求」中的success方法就是一個(gè)回調(diào)函數(shù),回調(diào)函數(shù)中執(zhí)行的是我們請(qǐng)求成功之后要做的進(jìn)一步操作。
這樣就初步實(shí)現(xiàn)了異步,但是回調(diào)函數(shù)有一個(gè)非常嚴(yán)重的缺點(diǎn),那就是「回調(diào)地獄」的問(wèn)題。
大家可以試想一下,如果我們?cè)诨卣{(diào)函數(shù)里再發(fā)起一個(gè)ajax請(qǐng)求呢?那豈不是要在success函數(shù)里繼續(xù)寫一個(gè)ajax請(qǐng)求?那如果需要多級(jí)嵌套發(fā)起ajax請(qǐng)求呢?豈不是需要多級(jí)嵌套?
如果嵌套的層級(jí)很深的話,我們的代碼結(jié)構(gòu)可能就會(huì)變成這樣:
因此,為了解決回調(diào)地獄的問(wèn)題,提出了「promise」、「async/await」、「generator」的概念。
2、Promise
「Promise」作為典型的微任務(wù)之一,它的出現(xiàn)可以使JS達(dá)到異步執(zhí)行的效果。
一個(gè)「Promise函數(shù)」的結(jié)構(gòu)如下列代碼如下:
const promise = new Promise((resolve, reject) => { resolve('a'); }); promise .then((arg) => { console.log(`執(zhí)行resolve,參數(shù)是${arg}`) }) .catch((arg) => { console.log(`執(zhí)行reject,參數(shù)是${arg}`) }) .finally(() => { console.log('結(jié)束promise') });
如果我們需要嵌套執(zhí)行異步代碼,相比于回調(diào)函數(shù)來(lái)說(shuō),「Promise」的執(zhí)行方式如下列代碼所示:
const promise = new Promise((resolve, reject) => { resolve(1); }); promise.then((value) => { console.log(value); return value * 2; }).then((value) => { console.log(value); return value * 2; }).then((value) => { console.log(value); }).catch((err) => { console.log(err); });
即通過(guò)then來(lái)實(shí)現(xiàn)多級(jí)嵌套(「鏈?zhǔn)秸{(diào)用」),這看起來(lái)是不是就比回調(diào)函數(shù)舒服多了~
每個(gè)「Promise」都會(huì)經(jīng)歷的生命周期是:
進(jìn)行中(pending) :此時(shí)代碼執(zhí)行尚未結(jié)束,所以也叫未處理的(unsettled)
已處理(settled) :異步代碼已執(zhí)行結(jié)束 已處理的代碼會(huì)進(jìn)入兩種狀態(tài)中的一種:已拒絕(rejected):遇到錯(cuò)誤,異步代碼執(zhí)行失敗 ,由reject()觸發(fā)
已完成(fulfilled):表明異步代碼執(zhí)行成功,由resolve()觸發(fā)
因此,「pending」,「fulfilled」, 「rejected」就是「Promise」中的三種狀態(tài)啦~
需要注意的是,在「Promise」中,要么包含resolve() 來(lái)表示 「Promise」 的狀態(tài)為fulfilled,要么包含 reject() 來(lái)表示「Promise」的狀態(tài)為rejected。
不然我們的「Promise」就會(huì)一直處于pending的狀態(tài),直至程序崩潰...
除此之外,「Promise」不僅很好的解決了鏈?zhǔn)秸{(diào)用的問(wèn)題,它還有很多高頻的操作:
·Promise.all(promises) :接收一個(gè)包含多個(gè)Promise對(duì)象的數(shù)組,等待所有都完成時(shí),返回存放它們結(jié)果的數(shù)組。如果任一被拒絕,則立即拋出錯(cuò)誤,其他已完成的結(jié)果會(huì)被忽略
·Promise.allSettled(promises) : 接收一個(gè)包含多個(gè)Promise對(duì)象的數(shù)組,等待所有都已完成或者已拒絕時(shí),返回存放它們結(jié)果對(duì)象的數(shù)組。每個(gè)結(jié)果對(duì)象的結(jié)構(gòu)為{status:'fulfilled' // 或 'rejected', value // 或reason}
·Promise.race(promises) : 接收一個(gè)包含多個(gè)Promise對(duì)象的數(shù)組,等待第一個(gè)有結(jié)果(完成/拒絕)的Promise,并把其result/error作為結(jié)果返回
示例代碼如下所示:
function getPromises(){ return [ new Promise(((resolve, reject) => setTimeout(() => resolve(1), 1000))), new Promise(((resolve, reject) => setTimeout(() => reject(new Error('2')), 2000))), new Promise(((resolve, reject) => setTimeout(() => resolve(3), 3000))), ]; } Promise.all(getPromises()).then(console.log); Promise.allSettled(getPromises()).then(console.log); Promise.race(getPromises()).then(console.log);
打印結(jié)果為:
3、Generator
「generator」是ES6提出的一種異步編程的方案。因?yàn)槭謩?dòng)創(chuàng)建一個(gè)iterator十分麻煩,因此ES6推出了「generator」,用于更方便的創(chuàng)建iterator。
也就是說(shuō),「generator」就是一個(gè)返回值為iterator對(duì)象的函數(shù)。
在講「generator」之前,我們先來(lái)看看iterator是什么:
?iterator中文名叫「迭代器」。它為js中各種不同的數(shù)據(jù)結(jié)構(gòu)(Object、Array、Set、Map)提供統(tǒng)一的訪問(wèn)機(jī)制。
?
任何數(shù)據(jù)結(jié)構(gòu)只要部署iterator接口,就可以完成遍歷操作。
因此iterator也是一種對(duì)象,不過(guò)相比于普通對(duì)象來(lái)說(shuō),它有著專為迭代而設(shè)計(jì)的接口。
我們通過(guò)一個(gè)例子來(lái)看看generator的特征:
function* createIterator() { yield 1; yield 2; yield 3; } // generators可以像正常函數(shù)一樣被調(diào)用,不同的是會(huì)返回一個(gè) iterator let iterator = createIterator(); console.log(iterator.next().value); // 1 console.log(iterator.next().value); // 2 console.log(iterator.next().value); // 3
形式上,「generator」 函數(shù)是一個(gè)普通函數(shù),但是有兩個(gè)特征:
·function關(guān)鍵字與函數(shù)名之間有一個(gè)星號(hào)
·函數(shù)體內(nèi)部使用yield語(yǔ)句,定義不同的內(nèi)部狀態(tài)
在普通函數(shù)中,我們想要一個(gè)函數(shù)最終的執(zhí)行結(jié)果,一般都是return出來(lái),或者以return作為結(jié)束函數(shù)的標(biāo)準(zhǔn)。運(yùn)行函數(shù)時(shí)也不能被打斷,期間也不能從外部再傳入值到函數(shù)體內(nèi)。
但在「generator」中,就打破了這幾點(diǎn),所以「generator」和普通的函數(shù)完全不同。
當(dāng)以function* 的方式聲明了一個(gè)「generator」生成器時(shí),內(nèi)部是可以有許多狀態(tài)的,以yield進(jìn)行斷點(diǎn)間隔。期間我們執(zhí)行調(diào)用這個(gè)生成的「generator」,他會(huì)返回一個(gè)遍歷器對(duì)象,用這個(gè)對(duì)象上的方法,實(shí)現(xiàn)獲得一個(gè)yield后面輸出的結(jié)果。
function* generator() { yield 1 yield 2 }; let iterator = generator(); iterator.next() // {value: 1, done: false} iterator.next() // {value: 2, done: false} iterator.next() // {value: undefined, done: true}
4、 async/await
最后我們來(lái)講講「async/await」,終于講到這兒了?。?!
「async/await」是ES7提出的關(guān)于異步的終極解決方案。我看網(wǎng)上關(guān)于「async/await」是誰(shuí)的語(yǔ)法糖這塊有兩個(gè)版本:
第一個(gè)版本說(shuō)「async/await」是Generator的語(yǔ)法糖
第二個(gè)版本說(shuō)「async/await」是Promise的語(yǔ)法糖
其實(shí),這兩種說(shuō)法都沒(méi)有錯(cuò)。
「關(guān)于async/await是Generator的語(yǔ)法糖:」
所謂generator語(yǔ)法糖,表明的就是「aysnc/await」實(shí)現(xiàn)的就是generator實(shí)現(xiàn)的功能。但是「async/await」比generator要好用。因?yàn)間enerator執(zhí)行yield設(shè)下的斷點(diǎn)采用的方式就是不斷的調(diào)用iterator方法,這是個(gè)手動(dòng)調(diào)用的過(guò)程。
而async配合await得到的就是斷點(diǎn)執(zhí)行后的結(jié)果。因此「async/await」比generator使用更普遍。
「關(guān)于async/await是Promise的語(yǔ)法糖:」
如果不使用「async/await」的話,Promise就需要通過(guò)鏈?zhǔn)秸{(diào)用來(lái)依次執(zhí)行then之后的代碼:
function counter(n){ return new Promise((resolve, reject) => { resolve(n + 1); }); } function adder(a, b){ return new Promise((resolve, reject) => { resolve(a + b); }); } function delay(a){ return new Promise((resolve, reject) => { setTimeout(() => resolve(a), 1000); }); } // 鏈?zhǔn)秸{(diào)用寫法 function callAll(){ counter(1) .then((val) => adder(val, 3)) .then((val) => delay(val)) .then(console.log); } callAll();//5
雖然相比于回調(diào)地獄來(lái)說(shuō),鏈?zhǔn)秸{(diào)用確實(shí)順眼多了。但是其呈現(xiàn)仍然略繁瑣了一些。
而「async/await的出現(xiàn),就使得我們可以通過(guò)同步代碼來(lái)達(dá)到異步的效果」:
async function callAll(){ const count = await counter(1); const sum = await adder(count, 3); console.log(await delay(sum)); } callAll();// 5
由此可見(jiàn),「Promise搭配async/await的使用才是正解!」
總結(jié)
到此這篇關(guān)于JS四種異步解決方案的文章就介紹到這了,更多相關(guān)JS異步解決方案內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
微信小程序h5頁(yè)面跳轉(zhuǎn)小程序的超詳細(xì)講解
開發(fā)中涉及到一個(gè)需求,就是從一個(gè)預(yù)約票購(gòu)買的頁(yè)面需要跳轉(zhuǎn)到?小程序,下面這篇文章主要給大家介紹了關(guān)于微信小程序h5頁(yè)面跳轉(zhuǎn)小程序的超詳細(xì)講解,文中通過(guò)實(shí)例代碼介紹的非常詳細(xì),需要的朋友可以參考下2023-02-02微信小程序填寫用戶頭像和昵稱實(shí)現(xiàn)方法淺析
這篇文章主要介紹了微信小程序填寫用戶頭像和昵稱實(shí)現(xiàn)方法,我們使用小程序往往能碰到提示允許獲取用戶頭像昵稱,這種功能怎么實(shí)現(xiàn)呢?本篇文章帶你探索2023-02-02js實(shí)現(xiàn)文字垂直滾動(dòng)和鼠標(biāo)懸停效果
這篇文章主要介紹了js實(shí)現(xiàn)文字垂直滾動(dòng)和鼠標(biāo)懸停效果,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2015-12-12js 復(fù)制功能 支持 for IE/FireFox/mozilla/ns
js 復(fù)制功能 支持 for IE/FireFox/mozilla/ns...2007-11-11解決bootstrap-select 動(dòng)態(tài)加載數(shù)據(jù)不顯示的問(wèn)題
今天小編就為大家分享一篇解決bootstrap-select 動(dòng)態(tài)加載數(shù)據(jù)不顯示的問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2018-08-08ES6基礎(chǔ)之展開語(yǔ)法(Spread syntax)
這篇文章主要介紹了ES6基礎(chǔ)之展開語(yǔ)法(Spread syntax),主要介紹了擴(kuò)展語(yǔ)法的使用,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2019-02-02