前言
異步編程模式在前端開(kāi)發(fā)過(guò)程中,顯得越來(lái)越重要。從最開(kāi)始的XHR到封裝后的Ajax都在試圖解決異步編程過(guò)程中的問(wèn)題。隨著ES6新標(biāo)準(zhǔn)的到來(lái),處理異步數(shù)據(jù)流又有了新的方案。我們都知道,在傳統(tǒng)的ajax請(qǐng)求中,當(dāng)異步請(qǐng)求之間的數(shù)據(jù)存在依賴關(guān)系的時(shí)候,就可能產(chǎn)生很難看的多層回調(diào),俗稱'回調(diào)地獄'(callback hell),這卻讓人望而生畏,Promise的出現(xiàn)讓我們告別回調(diào)函數(shù),寫(xiě)出更優(yōu)雅的異步代碼。在實(shí)踐過(guò)程中,卻發(fā)現(xiàn)Promise并不完美,Async/Await是近年來(lái)JavaScript添加的最革命性的的特性之一,Async/Await提供了一種使得異步代碼看起來(lái)像同步代碼的替代方法。接下來(lái)我們介紹這兩種處理異步編程的方案。
一、Promise的原理與基本語(yǔ)法
1.Promise的原理
Promise 是一種對(duì)異步操作的封裝,可以通過(guò)獨(dú)立的接口添加在異步操作執(zhí)行成功、失敗時(shí)執(zhí)行的方法。主流的規(guī)范是 Promises/A+。
Promise中有幾個(gè)狀態(tài):
- pending: 初始狀態(tài), 非 fulfilled 或 rejected;
- fulfilled: 成功的操作,為表述方便,fulfilled 使用 resolved 代替;
-
rejected: 失敗的操作。
pending可以轉(zhuǎn)化為fulfilled或rejected并且只能轉(zhuǎn)化一次,也就是說(shuō)如果pending轉(zhuǎn)化到fulfilled狀態(tài),那么就不能再轉(zhuǎn)化到rejected。并且fulfilled和rejected狀態(tài)只能由pending轉(zhuǎn)化而來(lái),兩者之間不能互相轉(zhuǎn)換。
2.Promise的基本語(yǔ)法
- Promise實(shí)例必須實(shí)現(xiàn)then這個(gè)方法
- then()必須可以接收兩個(gè)函數(shù)作為參數(shù)
- then()返回的必須是一個(gè)Promise實(shí)例
<script src="https://cdn.bootcss.com/bluebird/3.5.1/bluebird.min.js"></script>//如果低版本瀏覽器不支持Promise,通過(guò)cdn這種方式
<script>
function loadImg(src) {
var promise = new Promise(function(resolve, reject) {
var img = document.createElement('img')
img.onload = function() {
resolve(img)
}
img.onerror = function() {
reject('圖片加載失敗')
}
img.src = src
})
return promise
}
var src = 'https://www.imooc.com/static/img/index/logo_new.png'
var result = loadImg(src)
result.then(
function(img) {
console.log(1, img.width)
return img
},
function() {
console.log('error 1')
}
).then(function(img) {
console.log(2, img.height)
})
</script>
二、Promise多個(gè)串聯(lián)操作
Promise還可以做更多的事情,比如,有若干個(gè)異步任務(wù),需要先做任務(wù)1,如果成功后再做任務(wù)2,任何任務(wù)失敗則不再繼續(xù)并執(zhí)行錯(cuò)誤處理函數(shù)。要串行執(zhí)行這樣的異步任務(wù),不用Promise需要寫(xiě)一層一層的嵌套代碼。
有了Promise,我們只需要簡(jiǎn)單地寫(xiě)job1.then(job2).then(job3).catch(handleError);
其中job1、job2和job3都是Promise對(duì)象。
比如我們想實(shí)現(xiàn)第一個(gè)圖片加載完成后,再加載第二個(gè)圖片,如果其中有一個(gè)執(zhí)行失敗,就執(zhí)行錯(cuò)誤函數(shù):
var src1 = 'https://www.imooc.com/static/img/index/logo_new.png'
var result1 = loadImg(src1) //result1是Promise對(duì)象
var src2 = 'https://img1.mukewang.com/545862fe00017c2602200220-100-100.jpg'
var result2 = loadImg(src2) //result2是Promise對(duì)象
result1.then(function (img1) {
console.log('第一個(gè)圖片加載完成', img1.width)
return result2 // 鏈?zhǔn)讲僮?}).then(function (img2) {
console.log('第二個(gè)圖片加載完成', img2.width)
}).catch(function (ex) {
console.log(ex)
})
這里需注意的是:then 方法可以被同一個(gè) promise 調(diào)用多次,then 方法必須返回一個(gè) promise 對(duì)象。上例中result1.then如果沒(méi)有明文返回Promise實(shí)例,就默認(rèn)為本身Promise實(shí)例即result1,result1.then返回了result2實(shí)例,后面再執(zhí)行.then實(shí)際上執(zhí)行的是result2.then
三、Promise常用方法
除了串行執(zhí)行若干異步任務(wù)外,Promise還可以并行執(zhí)行異步任務(wù)。
試想一個(gè)頁(yè)面聊天系統(tǒng),我們需要從兩個(gè)不同的URL分別獲得用戶的個(gè)人信息和好友列表,這兩個(gè)任務(wù)是可以并行執(zhí)行的,用Promise.all()實(shí)現(xiàn)如下:
var p1 = new Promise(function (resolve, reject) {
setTimeout(resolve, 500, 'P1');
});
var p2 = new Promise(function (resolve, reject) {
setTimeout(resolve, 600, 'P2');
});
// 同時(shí)執(zhí)行p1和p2,并在它們都完成后執(zhí)行then:
Promise.all([p1, p2]).then(function (results) {
console.log(results); // 獲得一個(gè)Array: ['P1', 'P2']
});
有些時(shí)候,多個(gè)異步任務(wù)是為了容錯(cuò)。比如,同時(shí)向兩個(gè)URL讀取用戶的個(gè)人信息,只需要獲得先返回的結(jié)果即可。這種情況下,用Promise.race()實(shí)現(xiàn):
var p1 = new Promise(function (resolve, reject) {
setTimeout(resolve, 500, 'P1');
});
var p2 = new Promise(function (resolve, reject) {
setTimeout(resolve, 600, 'P2');
});
Promise.race([p1, p2]).then(function (result) {
console.log(result); // 'P1'
});
由于p1執(zhí)行較快,Promise的then()將獲得結(jié)果'P1'。p2仍在繼續(xù)執(zhí)行,但執(zhí)行結(jié)果將被丟棄。
總結(jié):Promise.all接受一個(gè)promise對(duì)象的數(shù)組,待全部完成之后,統(tǒng)一執(zhí)行success;
Promise.race接受一個(gè)包含多個(gè)promise對(duì)象的數(shù)組,只要有一個(gè)完成,就執(zhí)行success
接下來(lái)我們對(duì)上面的例子做下修改,加深對(duì)這兩者的理解:
var src1 = 'https://www.imooc.com/static/img/index/logo_new.png'
var result1 = loadImg(src1)
var src2 = 'https://img1.mukewang.com/545862fe00017c2602200220-100-100.jpg'
var result2 = loadImg(src2)
Promise.all([result1, result2]).then(function(datas) {
console.log('all', datas[0]) //<img src="https://www.imooc.com/static/img/index/logo_new.png">
console.log('all', datas[1]) //<img src="https://img1.mukewang.com/545862fe00017c2602200220-100-100.jpg">
})
Promise.race([result1, result2]).then(function(data) {
console.log('race', data) //<img src="https://img1.mukewang.com/545862fe00017c2602200220-100-100.jpg">
})
如果我們組合使用Promise,就可以把很多異步任務(wù)以并行和串行的方式組合起來(lái)執(zhí)行
四、Async/Await簡(jiǎn)介與用法
異步操作是 JavaScript 編程的麻煩事,很多人認(rèn)為async函數(shù)是異步操作的終極解決方案。
1、Async/Await簡(jiǎn)介
- async/await是寫(xiě)異步代碼的新方式,優(yōu)于回調(diào)函數(shù)和Promise。
- async/await是基于Promise實(shí)現(xiàn)的,它不能用于普通的回調(diào)函數(shù)。
- async/await與Promise一樣,是非阻塞的。
- async/await使得異步代碼看起來(lái)像同步代碼,再也沒(méi)有回調(diào)函數(shù)。但是改變不了JS單線程、異步的本質(zhì)。
2、Async/Await的用法
- 使用await,函數(shù)必須用async標(biāo)識(shí)
- await后面跟的是一個(gè)Promise實(shí)例
- 需要安裝babel-polyfill,安裝后記得引入 //npm i --save-dev babel-polyfill
function loadImg(src) {
const promise = new Promise(function(resolve, reject) {
const img = document.createElement('img')
img.onload = function() {
resolve(img)
}
img.onerror = function() {
reject('圖片加載失敗')
}
img.src = src
})
return promise
}
const src1 = 'https://www.imooc.com/static/img/index/logo_new.png'
const src2 = 'https://img1.mukewang.com/545862fe00017c2602200220-100-100.jpg'
const load = async function() {
const result1 = await loadImg(src1)
console.log(result1)
const result2 = await loadImg(src2)
console.log(result2)
}
load()
當(dāng)函數(shù)執(zhí)行的時(shí)候,一旦遇到 await 就會(huì)先返回,等到觸發(fā)的異步操作完成,再接著執(zhí)行函數(shù)體內(nèi)后面的語(yǔ)句。
五、Async/Await錯(cuò)誤處理
await 命令后面的 Promise 對(duì)象,運(yùn)行結(jié)果可能是 rejected,所以最好把 await 命令放在 try...catch 代碼塊中。try..catch錯(cuò)誤處理也比較符合我們平常編寫(xiě)同步代碼時(shí)候處理的邏輯。
async function myFunction() {
try {
await somethingThatReturnsAPromise();
} catch (err) {
console.log(err);
}
}
六、為什么Async/Await更好?
Async/Await較Promise有諸多好處,以下介紹其中三種優(yōu)勢(shì):
1. 簡(jiǎn)潔
使用Async/Await明顯節(jié)約了不少代碼。我們不需要寫(xiě).then,不需要寫(xiě)匿名函數(shù)處理Promise的resolve值,也不需要定義多余的data變量,還避免了嵌套代碼。
2. 中間值
你很可能遇到過(guò)這樣的場(chǎng)景,調(diào)用promise1,使用promise1返回的結(jié)果去調(diào)用promise2,然后使用兩者的結(jié)果去調(diào)用promise3。你的代碼很可能是這樣的:
const makeRequest = () => {
return promise1()
.then(value1 => {
return promise2(value1)
.then(value2 => {
return promise3(value1, value2)
})
})
}
使用async/await的話,代碼會(huì)變得異常簡(jiǎn)單和直觀
const makeRequest = async () => {
const value1 = await promise1()
const value2 = await promise2(value1)
return promise3(value1, value2)
}
3.條件語(yǔ)句
下面示例中,需要獲取數(shù)據(jù),然后根據(jù)返回?cái)?shù)據(jù)決定是直接返回,還是繼續(xù)獲取更多的數(shù)據(jù)。
const makeRequest = () => {
return getJSON()
.then(data => {
if (data.needsAnotherRequest) {
return makeAnotherRequest(data)
.then(moreData => {
console.log(moreData)
return moreData
})
} else {
console.log(data)
return data
}
})
}
代碼嵌套(6層)可讀性較差,它們傳達(dá)的意思只是需要將最終結(jié)果傳遞到最外層的Promise。使用async/await編寫(xiě)可以大大地提高可讀性:
const makeRequest = async () => {
const data = await getJSON()
if (data.needsAnotherRequest) {
const moreData = await makeAnotherRequest(data);
console.log(moreData)
return moreData
} else {
console.log(data)
return data
}
}
作者:浪里行舟
鏈接:異步解決方案----Promise與Await
來(lái)源:github
著作權(quán)歸作者所有。商業(yè)轉(zhuǎn)載請(qǐng)聯(lián)系作者獲得授權(quán),非商業(yè)轉(zhuǎn)載請(qǐng)注明出處。
