在MDN中,定義promise的只有一句話:promise對象用於表示一個異步操做的最終完成(或失敗),及其結果值。javascript
從這句話的定義咱們能夠抓住幾個關鍵詞:promise是對象、異步操做、最終狀態及結果值。java
在真正瞭解promise是什麼前,咱們不得不思考,promise的出現到底是爲了解決什麼問題。ajax
背景promise
javascript是單線程語言:單線程指若是有多個任務必須先排隊,前面的任務執行完成後,後面的任務再執行。瀏覽器
若是在函數返回結果的時候,調用者可以拿到預期的結果(就是函數計算的結果),那麼這個函數就是同步函數。bash
console.log('joseydong'); // 執行後,得到了返回結果
function wait(){
var time = (new Date()).getTime();//獲取當前的unix時間戳
while((new Date()).getTime() - time < 5000){}
console.log('你好個人名字是');
}
wait();
console.log('josey');
console.log('說得太慢了');
複製代碼
在這段代碼中,wait函數是一個須要耗時5秒的函數,在這5秒中,下面的console.log()函數只能等待。異步
這就是同步的缺點:若是一個函數是同步的,即便調用函數執行任務比較耗時,也會一直等待直到獲得執行結果。async
若是在函數返回的時候,調用者還不能獲得預期的結果,而是未來經過必定的手段獲得,這就叫異步。函數
當執行異步函數的時候,發出調用以後會立刻返回,但不會是返回預期結果;調用者沒必要默默等待,當獲得返回結果的時候回經過回調函數主動通知調用者。優化
異步操做是會在某個時間點觸發一個函數的調用。
好比AJAX就是典型的異步操做:
request.onreadystatechange = function () {
if (request.readyState === 4) {
if (request.status === 200) {
return success(request.responseText);
} else {
return fail(request.status);
}
}
}
複製代碼
把回調函數success(request.responseText)和fail(request.status)寫在一個ajax操做裏,不利於維護,也很差複用。
思考一種更好的寫法,好比:
var ajax = ajaxGet('url');
ajax.ifSuccess(success)
.ifFail(fail)
複製代碼
這種鏈式寫法的好處在於:先統一執行ajax邏輯,不關心如何處理返回結果,而後根據返回結果是成功仍是失敗,在未來的某個時候調用success函數或者fail函數。
這種承諾(promise)未來會執行的對象被稱爲promise對象。
Promise有各類開源實現,在ES6中被統一規範,由瀏覽器直接支持。
promise是一個對象,從這個對象中能夠獲取異步操做的信息。
它表明一個異步操做,有三種狀態:
有了promise,就能夠將異步操做以同步操做的流程表達,但它也有以下缺點:
建立promise
var promise = new Promise(
/* executor */
function(resolve,reject){
//...
}
);
複製代碼
executor函數由Promise實例當即執行,傳遞resolved和reject函數。
在executor內部,promise有以下可能的變化:
一、若是在executor方法的執行過程當中拋出了任何異常,那麼promise當即被拒絕,至關於reject方法被調用,executor的返回值也就被忽略。
二、若是一個promise對象處在resolved或者rejected狀態,那麼也能夠被稱爲settled狀態。
處理Promise實例
1.Promise對象的錯誤具備冒泡性質,會一直向後傳遞,直到被捕獲爲止。也就是說錯誤必定會被catch語句捕獲。
將多個Promise實例,包裝成一個新的Promise實例
Promise.all(iterable):當全部在可迭代參數中的promises已完成時,或者當傳遞過程當中的任何一個promise進入rejected狀態,返回promise。 var promise = Promise.all([p1,p2,p3]);
p1&&p2&&p3 都返回resolved => promise返回resolved
p1||p2||p3中任意一個返回rejected=>promise狀態就變成rejected,此時第一個被reject的實例返回值會傳遞給p的回調函數。
複製代碼
防止then嵌套 由於then中return的仍是promise,因此會執行完裏面的promise再執行外面的then。此時最好將其展開,也是同樣的結果,並且會更好讀。
// ------------ 很差的寫法 -------------
new Promise (resolve => {
console.log('Step 1');
setTimeout(()=> {
resolve('100');
},1000)
}).then(value => { // value => 100
return new Promise(resolve => {
console.log('Step 1-1');
setTimeout(() => {
resolve('110');
},1000);
})
.then(value => { // value => 110
console.log('Step 1-2');
return value;
})
.then(value => { // value => 110
console.log('Step 1-3')
return value;
})
})
.then(value => {
console.log(value); // value = 110
console.log('Step 2')
})
複製代碼
// ------------ 好的寫法 ------------
new Promise((resolve) => {
console.log("Step 1");
setTimeout(() => {
resolve("100");
}, 1000);
})
.then((value) => {
// value => 100
return new Promise((resolve) => {
console.log("Step 1-1");
setTimeout(() => {
resolve("110");
}, 1000);
});
})
.then((value) => {
// value => 110
console.log("Step 1-2");
return value;
})
.then((value) => {
// value => 110
console.log("Step 1-3");
return value;
})
.then((value) => {
console.log(value); // value = 110
console.log("Step 2");
});
複製代碼
使用.catch()捕捉錯誤
一般狀況下promise有以下兩種處理方式:
// ------------ 很差的寫法 -------------
promise.then(function(data) {
// success
}, function(err) { //僅處理promise運行時發生的錯誤。沒法處理回調中的錯誤
// error
});
// ------------ 好的寫法 ------------
promise.then(res => {
// success
}).catch(err => { // 處理 promise 和 前一個回調函數運行時發生的錯誤
// error
});
複製代碼
由於promise拋出的錯誤不會傳遞到外層,當使用第一種寫法時,成功回調的錯誤沒法處理,所以建議使用catch方法。
async:異步,await:異步等待。
簡單來講async用於聲明一個function是異步的,而await用於等待一個異步方法執行完成。
語法規定await只能出如今async函數中,async函數返回的是一個Promise對象。
promise的特色是無需等待,因此在沒有await的狀況下執行async函數,它會當即執行,返回一個promise對象而且不會阻塞後面的語句,就和普通的promise同樣。
await等待的是一個promise對象/其餘值。
由於async函數返回的是一個promise對象,因此await能夠用於等待一個async函數的返回值。而且,它能夠等待任意表達式的結果,因此await後面能夠接普通函數調用或者直接量。
function getSomething() {
return "something";
}
async function testAsync() {
return Promise.resolve("hello async");
}
async function test() {
const v1 = await getSomething(); // Promise對象
const v2 = await testAsync(); // 普通函數
console.log(v1, v2);
}
test();
複製代碼
await是個運算符,用於組成表達式,await表達式的運算結果取決於它等的東西。
若是等到的不是個promise對象,那麼await表達式的運算結果就等於它等到的東西。
若是等到的是個promise對象,那麼await就會阻塞後面的代碼,等着promise對象的resolved狀態,而後獲得resolve的值,做爲await表達式的運算結果。
一、這就是await必須放在async函數內部的緣由:async函數調用不會形成阻塞,它內部全部的阻塞都被封裝在一個promise對象中異步執行。
不使用async/await
function takeLongTime() {
return new Promise(resolve => {
setTimeout(() => resolve('hahahha'),1000);
});
}
takeLongTime().then(value => console.log('heihei',value))
複製代碼
使用async/await
function takeLongTime() {
return new Promise(resolve => {
setTimeout(() => resolve('hahahha'),1000);
});
}
async function test() {
const value = await takeLongTime();
console.log(value);
}
test();
複製代碼
async/await的優點在於處理then鏈
Promise經過then鏈來解決多層回調問題,但若是須要同時處理由多個Promise組成的then鏈,就能夠用async/await來進一步優化。
假設一個場景,分多個步驟完成,每一個步驟都是異步的,並且依賴於上一個步驟的結果。
function takeLongTime(n) {
return new Promise(resolve => {
setTimeout(() => resolve(n + 200), n);
});
}
function step1(n) {
console.log(`step1 with ${n}`);
return takeLongTime(n);
}
function step2(n) {
console.log(`step2 with ${n}`);
return takeLongTime(n);
}
function step3(n) {
console.log(`step3 with ${n}`);
return takeLongTime(n);
}
複製代碼
用Promise方法來實現這幾個步驟的處理:
function doIt() {
console.time("doIt");
const time1 = 300;
step1(time1)
.then(time2 => step2(time2))
.then(time3 => step3(time3))
.then(result => {
console.log(`result is ${result}`);
console.timeEnd("doIt");
});
}
doIt();
// step1 with 300
// step2 with 500
// step3 with 700
// result is 900
// doIt: 1580.487ms
複製代碼
用async/await來實現:
async function doIt() {
console.time("doIt");
const time1 = 300;
const time2 = await step1(time1);
const time3 = await step2(time2);
const result = await step3(time3);
console.log(`result is ${result}`);
console.timeEnd("doIt");
}
doIt();
複製代碼
一、async/await優勢:代碼更加簡潔。
修改下上面場景的代碼,後續步驟須要多個返回值:
function step1(n) {
console.log(`step1 with ${n}`);
return takeLongTime(n);
}
function step2(m, n) {
console.log(`step2 with ${m} and ${n}`);
return takeLongTime(m + n);
}
function step3(k, m, n) {
console.log(`step3 with ${k}, ${m} and ${n}`);
return takeLongTime(k + m + n);
}
複製代碼
用promise處理,就會發現處理返回值會比較麻煩:
function doIt() {
console.time("doIt");
const time1 = 300;
step1(time1)
.then(time2 => {
return step2(time1, time2)
.then(time3 => [time1, time2, time3]);
})
.then(times => {
const [time1, time2, time3] = times;
return step3(time1, time2, time3);
})
.then(result => {
console.log(`result is ${result}`);
console.timeEnd("doIt");
});
}
doIt();
複製代碼
用async/await處理:
async function doIt() {
console.time("doIt");
const time1 = 300;
const time2 = await step1(time1);
const time3 = await step2(time1, time2);
const result = await step3(time1, time2, time3);
console.log(`result is ${result}`);
console.timeEnd("doIt");
}
doIt();
// step1 with 300
// step2 with 800 = 300 + 500
// step3 with 1800 = 300 + 500 + 1000
// result is 2000
// doIt: 2907.387ms
複製代碼
二、async/await的優勢:解決promise傳遞參數太麻煩的問題。
async/await使用try...catch處理rejected狀態
async function myFunction() {
try {
await somethingThatReturnsAPromise();
} catch (err) {
console.log(err);
}
}
// 另外一種寫法
async function myFunction() {
await somethingThatReturnsAPromise().catch(function (err){
console.log(err);
});
}
複製代碼