異步編程模式在前端開發過程當中,顯得愈來愈重要。從最開始的XHR到封裝後的Ajax都在試圖解決異步編程過程當中的問題。隨着ES6新標準的到來,處理異步數據流又有了新的方案。咱們都知道,在傳統的ajax請求中,當異步請求之間的數據存在依賴關係的時候,就可能產生很難看的多層回調,俗稱'回調地獄'(callback hell),這卻讓人望而生畏,Promise的出現讓咱們告別回調函數,寫出更優雅的異步代碼。在實踐過程當中,卻發現Promise並不完美,Async/Await是近年來JavaScript添加的最革命性的的特性之一,Async/Await提供了一種使得異步代碼看起來像同步代碼的替代方法。接下來咱們介紹這兩種處理異步編程的方案。javascript
Promise 是一種對異步操做的封裝,能夠經過獨立的接口添加在異步操做執行成功、失敗時執行的方法。主流的規範是 Promises/A+。css
Promise中有幾個狀態:html
pending: 初始狀態, 非 fulfilled 或 rejected;前端
fulfilled: 成功的操做,爲表述方便,fulfilled 使用 resolved 代替;java
rejected: 失敗的操做。node
Promise實例必須實現then這個方法git
then()必須能夠接收兩個函數做爲參數github
then()返回的必須是一個Promise實例ajax
<script src="https://cdn.bootcss.com/bluebird/3.5.1/bluebird.min.js"></script>//若是低版本瀏覽器不支持Promise,經過cdn這種方式
<script type="text/javascript">
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還能夠作更多的事情,好比,有若干個異步任務,須要先作任務1,若是成功後再作任務2,任何任務失敗則再也不繼續並執行錯誤處理函數。要串行執行這樣的異步任務,不用Promise須要寫一層一層的嵌套代碼。npm
有了Promise,咱們只須要簡單地寫job1.then(job2).then(job3).catch(handleError);
其中job一、job2和job3都是Promise對象。
好比咱們想實現第一個圖片加載完成後,再加載第二個圖片,若是其中有一個執行失敗,就執行錯誤函數:
var src1 = 'https://www.imooc.com/static/img/index/logo_new.png'
var result1 = loadImg(src1) //result1是Promise對象
var src2 = 'https://img1.mukewang.com/545862fe00017c2602200220-100-100.jpg'
var result2 = loadImg(src2) //result2是Promise對象
result1.then(function (img1) {
console.log('第一個圖片加載完成', img1.width)
return result2 // 鏈式操做
}).then(function (img2) {
console.log('第二個圖片加載完成', img2.width)
}).catch(function (ex) {
console.log(ex)
})
複製代碼
這裏需注意的是:then 方法能夠被同一個 promise 調用屢次,then 方法必須返回一個 promise 對象。上例中result1.then若是沒有明文返回Promise實例,就默認爲自己Promise實例即result1,result1.then返回了result2實例,後面再執行.then實際上執行的是result2.then
除了串行執行若干異步任務外,Promise還能夠並行執行異步任務。
試想一個頁面聊天系統,咱們須要從兩個不一樣的URL分別得到用戶的我的信息和好友列表,這兩個任務是能夠並行執行的,用Promise.all()實現以下:
var p1 = new Promise(function (resolve, reject) {
setTimeout(resolve, 500, 'P1');
});
var p2 = new Promise(function (resolve, reject) {
setTimeout(resolve, 600, 'P2');
});
// 同時執行p1和p2,並在它們都完成後執行then:
Promise.all([p1, p2]).then(function (results) {
console.log(results); // 得到一個Array: ['P1', 'P2']
});
複製代碼
有些時候,多個異步任務是爲了容錯。好比,同時向兩個URL讀取用戶的我的信息,只須要得到先返回的結果便可。這種狀況下,用Promise.race()實現:
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執行較快,Promise的then()將得到結果'P1'。p2仍在繼續執行,但執行結果將被丟棄。
總結:Promise.all接受一個promise對象的數組,待所有完成以後,統一執行success;
Promise.race接受一個包含多個promise對象的數組,只要有一個完成,就執行success
接下來咱們對上面的例子作下修改,加深對這二者的理解:
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,就能夠把不少異步任務以並行和串行的方式組合起來執行
異步操做是 JavaScript 編程的麻煩事,不少人認爲async函數是異步操做的終極解決方案。
async/await是寫異步代碼的新方式,優於回調函數和Promise。
async/await是基於Promise實現的,它不能用於普通的回調函數。
async/await與Promise同樣,是非阻塞的。
async/await使得異步代碼看起來像同步代碼,再也沒有回調函數。可是改變不了JS單線程、異步的本質。
使用await,函數必須用async標識
await後面跟的是一個Promise實例
須要安裝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()
複製代碼
當函數執行的時候,一旦遇到 await 就會先返回,等到觸發的異步操做完成,再接着執行函數體內後面的語句。
await 命令後面的 Promise 對象,運行結果多是 rejected,因此最好把 await 命令放在 try...catch 代碼塊中。try..catch錯誤處理也比較符合咱們日常編寫同步代碼時候處理的邏輯。
async function myFunction() {
try {
await somethingThatReturnsAPromise();
} catch (err) {
console.log(err);
}
}
複製代碼
Async/Await較Promise有諸多好處,如下介紹其中三種優點:
使用Async/Await明顯節約了很多代碼。咱們不須要寫.then,不須要寫匿名函數處理Promise的resolve值,也不須要定義多餘的data變量,還避免了嵌套代碼。
你極可能遇到過這樣的場景,調用promise1,使用promise1返回的結果去調用promise2,而後使用二者的結果去調用promise3。你的代碼極可能是這樣的:
const makeRequest = () => {
return promise1()
.then(value1 => {
return promise2(value1)
.then(value2 => {
return promise3(value1, value2)
})
})
}
複製代碼
使用async/await的話,代碼會變得異常簡單和直觀
const makeRequest = async () => {
const value1 = await promise1()
const value2 = await promise2(value1)
return promise3(value1, value2)
}
複製代碼
下面示例中,須要獲取數據,而後根據返回數據決定是直接返回,仍是繼續獲取更多的數據。
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層)可讀性較差,它們傳達的意思只是須要將最終結果傳遞到最外層的Promise。使用async/await編寫能夠大大地提升可讀性:
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
}
}
複製代碼