前言
javascript是一門單線程的語言,也就是說一次只能完成一件任務(wù),如果有多個任務(wù),就需要排隊(duì)進(jìn)行處理。如果一個任務(wù)耗時很長,后面的任務(wù)也必須排隊(duì)等待,這樣大大的影響了整個程序的執(zhí)行。為了解決這個問題,javascript語言將任務(wù)分為兩種模式:
本文主要針對近兩年javascript的發(fā)展,主要介紹異步處理的進(jìn)化史。目前,在javascript異步處理中,有以下幾種方式:

callback
回調(diào)函數(shù)是最早解決異步編程的方法。無論是常見的setTimeout還是ajax請求,都是采用回調(diào)的形式把事情在某一固定的時刻進(jìn)行執(zhí)行。
//常見的:setTimeout setTimeout(function callback(){ console.log('aa'); }, 1000); //ajax請求 ajax(url,function callback(){ console.log("ajax success",res); })回調(diào)函數(shù)的處理一般將函數(shù)callback作為參數(shù)傳進(jìn)函數(shù),在合適的時候被調(diào)用執(zhí)行?;卣{(diào)函數(shù)的優(yōu)點(diǎn)就是簡單、容易理解和實(shí)現(xiàn),但有個致命的缺點(diǎn),容易出現(xiàn)回調(diào)地獄(Callback hell),即多個回調(diào)函數(shù)嵌套使用。造成代碼可讀性差、可維護(hù)性差且只能在回調(diào)中處理異常。
ajax(url, () => { //todo ajax(url1, () => { //todo ajax(url2, () => { //todo }) })})事件監(jiān)聽
事件監(jiān)聽采用的是事件驅(qū)動的模式。事件的執(zhí)行不取決于代碼的順序,而是某個事件的發(fā)生。
假設(shè)有兩個函數(shù),為f1綁定一個事件(jQuery的寫法),當(dāng)f1函數(shù)發(fā)生success事件時,執(zhí)行函數(shù)f2:
f1.on('success',f2);對f1進(jìn)行改寫:
function f1(){ ajax(url,() => { //todo f1.trigger('success');//觸發(fā)success事件,從而執(zhí)行f2函數(shù) })}事件監(jiān)聽的方式較容易理解,可以綁定多個事件,每個事件可以指定多個回調(diào)函數(shù),而且可以"去耦合",有利于實(shí)現(xiàn)模塊化。缺點(diǎn)是整個程序都要變成事件驅(qū)動型,運(yùn)行流程會變得很不清晰。閱讀代碼的時候,很難看出主流程。
發(fā)布訂閱
我們假定,存在一個"信號中心",某個任務(wù)執(zhí)行完成,就向信號中心"發(fā)布"(publish)一個信號,其他任務(wù)可以向信號中心"訂閱"(subscribe)這個信號,從而知道什么時候自己可以開始執(zhí)行。這就叫做 發(fā)布/訂閱模式(publish-subscribe pattern),又稱**觀察者模式"(observer pattern) **。
//利用jquery的插件實(shí)現(xiàn)//首先,f2向消息中心訂閱success事件jQuery.subscribe('success',f2);//對f1進(jìn)行改寫:function f1(){ ajax(url,() => { //todo jQuery.publish('success');//當(dāng)f1執(zhí)行完畢后,向消息中心jQuery發(fā)布success事件,從而執(zhí)行f2函數(shù) })}//f2執(zhí)行完畢后,可以取消訂閱jQuery.unsubscribe('success',f2)該方法和事件監(jiān)聽的性質(zhì)類似,但我們可以通過消息中心來查閱一共有多少個信號,每個信號有多少個訂閱者。
Promise
**Promise**是CommonJS工作組提出的一種規(guī)范,可以獲取異步操作的消息,也是異步處理中常用的一種解決方案。Promise的出現(xiàn)主要是用來解決回調(diào)地獄、支持多個并發(fā)的請求,獲取并發(fā)請求的數(shù)據(jù)并且解決異步的問題。
let p = new Promise((resolve, reject) => { //做一些異步操作 setTimeout(()=>{ let num = parseInt(Math.random()*100); if(num > 50){ resolve("num > 50"); // 如果數(shù)字大于50就調(diào)用成功的函數(shù),并且將狀態(tài)變成Resolved }else{ reject("num <50");// 否則就調(diào)用失敗的函數(shù),將狀態(tài)變成Rejected } },10000)});p.then((res) => { console.log(res);}).catch((err) =>{ console.log(err);})Promise有三種狀態(tài):等待pending、成功fulfied、失敗rejected;狀態(tài)一旦改變,就不會再變化,在Promise對象創(chuàng)建后,會馬上執(zhí)行。等待狀態(tài)可以變?yōu)閒ulfied狀態(tài)并傳遞一個值給相應(yīng)的狀態(tài)處理方法,也可能變?yōu)槭顟B(tài)rejected并傳遞失敗信息。任一一種情況出現(xiàn)時,Promise對象的 then 方法就會被調(diào)用(then方法包含兩個參數(shù):onfulfilled 和 onrejected,均為 Function。當(dāng)Promise狀態(tài)為fulfilled時,調(diào)用 then 的 onfulfilled 方法,當(dāng)Promise狀態(tài)為rejected時,調(diào)用 then 的 onrejected 方法)。
需要注意的是: Promise.prototype.then 和 Promise.prototype.catch 方法返回promise 對象, 所以可以被鏈?zhǔn)秸{(diào)用,如下圖:

Promise的方法:
Generators/yield
Generators是ES6提供的異步解決方案,其最大的特點(diǎn)就是可以控制函數(shù)的執(zhí)行??梢岳斫獬梢粋€內(nèi)部封裝了很多狀態(tài)的狀態(tài)機(jī),也是一個遍歷器對象生成函數(shù)。Generator 函數(shù)的特征:
舉個例子:
function* createIterator(x) { let y = yield (x+1) let z = 2*(yield(y/3)) return (x+y+z)}// generators可以像正常函數(shù)一樣被調(diào)用,不同的是會返回一個 iteratorlet iterator = createIterator(4);console.log(iterator.next()); // {value:5,done:false}console.log(iterator.next()); // {value:NaN,done:false}console.log(iterator.next()); // {value:NaN,done:true}let iterator1 = createIterator(4);//返回一個iterator//next傳參數(shù)console.log(iterator1.next()); // {value:5,done:false}console.log(iterator1.next(12)); // {value:4,done:false}console.log(iterator1.next(15)); // {value:46,done:true}代碼分析:
async/await
初入async/await
async/await在ES7提出,是目前在javascript異步處理的終極解決方案。
async 其本質(zhì)是 Generator 函數(shù)的語法糖。相較于Generator放入改進(jìn)如下:
語法分析
async語法
用來定義異步函數(shù),自動將函數(shù)轉(zhuǎn)換為promise對象,可以使用then來添加回調(diào),其內(nèi)部return的值作為then回調(diào)的參數(shù)。
async function f(){ return "hello async";}f().then((res) => { //通過then來添加回調(diào)且內(nèi)部返回的res作為回調(diào)的參數(shù) console.log(res); // hello async})在異步函數(shù)的內(nèi)部可以使用await,其返回的promise對象必須等到內(nèi)部所以await命令后的promise對象執(zhí)行完,才會發(fā)生狀態(tài)變化即執(zhí)行then回調(diào)。
const delay = function(timeout){ return new Promise(function(resolve){ return setTimeout(resolve, timeout); });}async function f(){ await delay(1000); await delay(2000); return '完成';}f().then(res => console.log(res));//需要等待3秒之后才會打印:完成await即表示異步等待,用來暫停異步函數(shù)的執(zhí)行,只能在異步函數(shù)和promise使用,且當(dāng)使用在promise前面,表示等待promise完成并返回結(jié)果。
async function f() { return await 1 //await后面不是Promise的話,也會被轉(zhuǎn)換為一個立即為resolve的promise};f().then( res => console.log("處理成功",res))//打印出:處理成功 1 .catch(err => console.log("處理是被",err))////打印出:Promise{<resolved>:undefined}錯誤處理
如果await后面的異步出現(xiàn)錯誤,等同于async返回的promise對象為reject,其錯誤會被catch的回調(diào)函數(shù)接收到。需要注意的是,當(dāng) async 函數(shù)中只要一個 await 出現(xiàn) reject 狀態(tài),則后面的 await 都不會被執(zhí)行。
let a;async function f(){ await Promise.reject("error") a = await 1 //該await并沒有執(zhí)行 }err().then(res => console.log(a))怎么處理呢,可以把第一個await放在try/catch,遇到函數(shù)的時候,可以將錯誤拋出并往下執(zhí)行。
async function f() { try{ await Promise.reject('error'); }catch(error){ console.log(error); } return await 1}f().then(res => console.log('成功', res))//成功打印出1如果有多個await處理,可以統(tǒng)一放在try/catch模塊中,而且async可以使得try/catch同時處理同步和異步錯誤。
總結(jié)
通過以上六種javascript異步處理的常用方法,可以看出async/await可以說是異步終極解決方案了,最后看一下async/await用得最多的場景:
如果一個業(yè)務(wù)需要很多個異步操作組成,并且每個步驟都依賴于上一步的執(zhí)行結(jié)果,這里采用不同的延時來體現(xiàn):
//首先定義一個延時函數(shù)function delay(time) { return new Promise(resolve => { setTimeout(() => resolve(time), time); });}//采用promise鏈?zhǔn)秸{(diào)用實(shí)現(xiàn)delay(500).then(result => { return delay(result + 1000)}).then(result => { return delay(result + 2000)}).then(result => { console.log(result) //3500ms后打印出3500}).catch(error => { console.log(error)}) //采用async實(shí)現(xiàn)async function f(){ const r1 = await delay(500) const r2 = await delay(r1+1000) const r3 = await delay(r2+2000) return r3}f().then(res =>{ console.log(res)}).catch(err=>{ console.log(err)})可以看出,采用promise實(shí)現(xiàn)采用了很多then進(jìn)行不停的鏈?zhǔn)秸{(diào)用,使得代碼變得冗長和復(fù)雜且沒有語義化。而 async/await首先使用同步的方法來寫異步,代碼非常清晰直觀,而且使代碼語義化,一眼就能看出代碼執(zhí)行的順序,最后 async 函數(shù)自帶執(zhí)行器,執(zhí)行的時候無需手動加載。
總結(jié)
以上就是這篇文章的全部內(nèi)容了,希望本文的內(nèi)容對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,謝謝大家對武林網(wǎng)的支持。
新聞熱點(diǎn)
疑難解答