Promise由淺入深
前言
我們都知道javaScript是單線程的,代碼執(zhí)行的順序是從上往下。但是在實際開發(fā)中難免會出現(xiàn)異步編程。所謂異步就是代碼執(zhí)行到這里不會立即執(zhí)行得出結(jié)果,比如ajax請求,文件的讀取等,為了不阻塞進程,先把它放到事件隊列中。
但是傳統(tǒng)的ajax的請求方式有一個很大問題,比如有多個請求并且下次的請求需要依賴上次的請求結(jié)果,那么就會出現(xiàn)多層嵌套,也就是所謂的“回調(diào)地獄”。
es6中提出了Promise。可以作為解決異步的一種方式。promise可以理解成是一個對象的代理,通常用來代理一個未來的值,為什么說是未來呢,因為這個值通常是由異步操作得出的,不像同步操作那樣立即就可以拿到值。
我們可以把Promise比做一個容器,它里邊包含著未來才會結(jié)束的事件的結(jié)果。
Promise有三種狀態(tài):
- pending 初始狀態(tài)
- fulfilled 成功完成
- rejected 失敗
1、 聲明未執(zhí)行的Promise實例是pending狀態(tài),執(zhí)行成功調(diào)用resolve方法是fulfilled狀態(tài),執(zhí)行失敗調(diào)用rejecte方法是rejected狀態(tài)。
2、 Promise狀態(tài)一旦改變,就不會在變了。也就是說狀態(tài)的改變只有兩種可能:(1)從pending變成fulfilled (2)從pending變成rejected。只要狀態(tài)一旦改變,狀態(tài)就凝固了,不會在發(fā)生改變。
Promise參數(shù)
Promise接受一個函數(shù)參數(shù),函數(shù)參數(shù)又有兩個參數(shù),分別是resolve和rejecte。在函數(shù)參數(shù)中執(zhí)行相應(yīng)的異步操作,當執(zhí)行成功,調(diào)用resolve,執(zhí)行失敗調(diào)用rejecte。通過reslove和rejecte傳遞結(jié)果。
Promise的前因后果
new Promise
var promise = new Promise(function (resolve, reject) {
console.log('create promise')
setTimeout(function () {
resolve('first value');
console.log('最后執(zhí)行');
}, 1000);
})
// 實例創(chuàng)建完成,函數(shù)內(nèi)部有異步操作,不會立即執(zhí)行resolve方法,所以狀態(tài)為pending
console.log(promise)
輸出先后順序:
"create promise"
Promise {[[PromiseStatus]]: "pending", [[PromiseValue]]: undefined}
"最后執(zhí)行"
從輸出的先后順序可以看出來Promise的創(chuàng)建以及函數(shù)參數(shù)的執(zhí)行是一個同步的過程,實例創(chuàng)建完成后由于函數(shù)參數(shù)中有一個異步操作,不會立即執(zhí)行resolve方法,所以Promise的實例仍是pending狀態(tài),。
Promise實例構(gòu)造函數(shù)的原型上有then方法和catch方法,可以被其實例所共享。其中then方法接受兩個函數(shù)參數(shù),第一個函數(shù)的參數(shù)就是resolve中接收的值,第二個函數(shù)是錯誤處理;catch方法是rejecte中的值。
Promise實例調(diào)用then方法的實現(xiàn)思路:
// 偽代碼
Promise.prototype.then = function(success, faile){
// then方法的偽代碼 執(zhí)行相應(yīng)的函數(shù)參數(shù)
// 如果異步操作成功執(zhí)行success方法
success();
// 反之
faile();
// 最后返回一個新的Promise對象
return newPromise;
}
鏈式調(diào)用
因為then方法的兩個函數(shù)參數(shù)默認會返回一個新的Promise對象,所以可以進行鏈式調(diào)用。調(diào)用規(guī)則:
- 當沒有顯式return時,默認返回新的Promise對象;
- 當return的不是Promise對象時,那么return的值會作為then返回的新Promise實例的resolved參數(shù);
- 當return的是一個Promise實例時,那么then返回的Promise實例就是該實例;
具體如下:
promise.then(function (response) {
console.log(response); // first value
console.log(promise); // Promise對象 狀態(tài)已經(jīng)由pending 變?yōu)?resolved
}).then(function (response) {
console.log(response); // undefined
// 從這里可以看出來,then()方法返回的是一個新的promise,因為新的Promise實例中并沒有執(zhí)行resolve()方法,更沒有什么異步操作,所以這里的值是undefined(聲明未初始化)
// 手動創(chuàng)建并返回一個新的Promise實例
return new Promise(function (resolve, reject) {
setTimeout(function () {
resolve('second value')
}, 1000);
})
}).then(function(response){
console.log(response); // second value
// 這里之所以能夠獲取到值,是因為上一個then()返回的Promise成功執(zhí)行異步操作
return '還可以返回非Promise實例'
}).then(function(response){
console.log(response) // 還可以返回非Promise實例
})
下面通過簡單的示例一步步深入了解。
一、Promise立即執(zhí)行
var promise = new Promise(function(resolved, rejected){
console.log('創(chuàng)建promise')
resolved('成功');
rejected('失敗');
})
console.log('執(zhí)行完成')
promise.then(function(res){
console.log(res);
}).catch(function(err){
console.log(err);
})
輸出結(jié)果:
創(chuàng)建Promise
執(zhí)行完成
成功
解釋:Promise對象表示將來某一時刻要發(fā)生的事件,但是創(chuàng)建(new)Promise是一個同步的過程,包括傳入的函數(shù)參數(shù)也是一個立即執(zhí)行的函數(shù),只是函數(shù)內(nèi)部是異步操作還是同步操作就不確定了。所以以上代碼是按順序輸出。
二、Promise的三種狀態(tài)
var p1 = new Promise(function(resolve,reject){
resolve(1);
});
var p2 = new Promise(function(resolve,reject){
setTimeout(function(){
resolve(2);
}, 500);
});
var p3 = new Promise(function(resolve,reject){
setTimeout(function(){
reject(3);
}, 500);
});
p1、p2、p3的狀態(tài)
console.log(p1); // resolved
console.log(p2); // pending
console.log(p3); // pending
setTimeout(function(){
console.log(p2); // resolved
}, 1000);
setTimeout(function(){
console.log(p3); // rejected
}, 1000);
p1.then(function(value){
console.log(value); // 1
});
p2.then(function(value){
console.log(value); // 2
});
p3.catch(function(err){
console.log(err); // 3
});
輸出結(jié)果:
Promise {[[PromiseStatus]]: "resolved", [[PromiseValue]]: 1}
Promise {[[PromiseStatus]]: "pending", [[PromiseValue]]: undefined}
Promise {[[PromiseStatus]]: "pending", [[PromiseValue]]: undefined}
1
2
3
Promise {[[PromiseStatus]]: "resolved", [[PromiseValue]]: 2}
Promise {[[PromiseStatus]]: "rejected", [[PromiseValue]]: 3}
解釋:其實我們可以把Promise內(nèi)部理解成是一個狀態(tài)機,分別有pending resolved rejected三種狀態(tài)。 當剛創(chuàng)建Promise時處于pending狀態(tài),所以p2,p3的狀態(tài)為pending,因為p1的函數(shù)參數(shù)執(zhí)行的是同步代碼,Promise剛創(chuàng)建完成resolved也就執(zhí)行了,所以p1是resolved狀態(tài)。然后又是兩個setTimeout函數(shù),所以會先分別執(zhí)行對應(yīng)的then方法,輸出 1、 2、 3;1s后執(zhí)行setTimeout函數(shù),此時p2 p3的狀態(tài)分別已經(jīng)變成了resolved和rejected。
三、Promise狀態(tài)不可逆
var p1 = new Promise(function(resolve, reject){
resolve("success1");
resolve("success2");
});
var p2 = new Promise(function(resolve, reject){
resolve("success");
reject("reject");
});
p1.then(function(value){
console.log(value);
});
p2.then(function(value){
console.log(value);
});
輸出結(jié)果:
'success1'
'success'
解釋:Promise狀態(tài)一旦從pending變成resolved或rejected時狀態(tài)就凝固了。不管再調(diào)用resolved或rejected都不管用。
四、鏈式調(diào)用
var p = new Promise(function (resolve, reject) {
resolve(1);
});
p.then(function (value) {
console.log(value); // 1
return value * 2;
}).then(function (value) {
console.log(value); // 2
}).then(function (value) {
console.log(value); // undefined
return Promise.resolve('resolve');
}).then(function (value) {
console.log(value); // resolve
return Promise.reject('reject');
}).then(function (value) {
console.log('resolve: ' + value);
}).catch(function(err){
console.log("rejected: ", err) // reject
})
輸出結(jié)果:
1
2
"undefined"
"resolved"
"rejected: rejected"
解釋: Promise對象的then方法返回一個新的Promise對象,因此可以鏈式調(diào)用。第一個then有返回值,所以在二個then中輸出2;而第二個then沒有返回值,所以第三個then輸出undefined;第三個then返回一個Promise的resolved方法,所以第四個then中輸出resolved;但是第四個then中返回Promise的rejected方法,所以第五個then的第一個函數(shù)參數(shù)不會輸出,而是在第二個函數(shù)參數(shù)或者catch中輸出rejected。
五、Promise then()回調(diào)的異步性
var promise = new Promise(function(resolved, rejected){
resolved('success')
console.log('first')
})
promise.then(function(res){
console.log(res)
})
console.log('second')
輸出結(jié)果:
'first'
'second'
'success'
解釋:new Promise、函數(shù)參數(shù)以及其內(nèi)部的代碼是一個同步的過程,所以會先輸出 first,但是then方法中的回調(diào)函數(shù)是異步的,所以先輸出 second,最后輸出 success。
六、Promise中的異常
var p1 = new Promise( function(resolve,reject){
foo.bar();
resolve( 1 );
});
console.log(p1)
// Promise {[[PromiseStatus]]: "rejected", [[PromiseValue]]: ReferenceError: foo is not defined}
p1.then(
function(value){
console.log('p1 then1 value: ' + value);
},
function(err){
console.log('p1 then1 err: ' + err);// 報錯 foo is not defined
// 沒有顯示返回
}
).catch(function(err){
console.log('err', err)
}).then(
function(value){
console.log('p1 then2 value: '+value); // p1 then2 value: undefined
},
function(err){
console.log('p1 then2 err: ' + err);
}
);
var p2 = new Promise(function(resolve,reject){
resolve( 2 );
});
p2.then(
function(value){
console.log('p2 then1 value: ' + value);// p2 then1 value: 2
foo.bar(); // 報錯
},
function(err){
console.log('p2 then1 err: ' + err);
}
).then(
function(value){
console.log('p2 then2 value: ' + value);
},
function(err){
console.log('p2 then2 err: ' + err); // p2 then2 err: foo is not defined
return 1;
}
).then(
function(value){
console.log('p2 then3 value: ' + value);// p2 then3 value: 1
},
function(err){
console.log('p2 then then then err: ' + err);
}
);
輸出結(jié)果:
p1 then1 err: ReferenceError: foo is not defined
p2 then1 value: 2
p2 then2 err: ReferenceError: foo is not defined
p1 then2 value: undefined
p2 then3 value: 1
解釋:Promise中的異常由then參數(shù)中第二個回調(diào)函數(shù)(Promise執(zhí)行失敗的回調(diào))處理,異常信息將作為Promise的值。異常一旦得到處理,then返回的后續(xù)Promise對象將恢復(fù)正常,并會被Promise執(zhí)行成功的回調(diào)函數(shù)處理。另外,p1、p2 多個then的回調(diào)是交替執(zhí)行的 ,這正是由Promise then回調(diào)的異步性決定的。
七、Promise.resolve()
Promise.resolve(value)返回給定給定值解析后的Promise對象。
value分三種情況:
- 普通值 --- 以該值作為resolved狀態(tài)返回promise
Promise.resolve("Success").then(function(value) {
console.log(value); // "Success"
}, function(value) {
// 不會被調(diào)用
});
- Promise對象 --- 返回值即為該傳入的Promise對象
let p1 = Promise.resolve('success');
let p2 = Promise.resolve(p1);
cast.then(value => {
console.log('value: ' + value);
});
console.log('p1 === p2 ? ' + (p1 === p2));
/*
* 打印順序如下,這里有一個同步異步先后執(zhí)行的區(qū)別
* p1 === p2 ? true
* value: success
*/
- 帶有then方法的對象 --- 返回的promise會“跟隨”這個thenable的對象,采用它的最終狀態(tài)(指resolved/rejected/pending/settled)
// Resolve一個thenable對象
var p1 = Promise.resolve({
then(onFulfill, onReject) {
onFulfill("fulfilled!");
}
});
console.log(p1 instanceof Promise) // true, 這是一個Promise對象
p1
.then(res => {
console.log(res); // "fulfilled!"
})
.catch(err => {
console.log(err)
})
// Thenable在callback之前拋出異常
let thenable = {
then(resolve) {
throw new Error("Throwing");
resolve("Resolving")
}
};
let p2 = Promise.resolve(thenable);
p2
.then(res => {
console.log(res)
})
.catch(err => {
console.log(err) // Throwing
})
// Thenable在callback之后拋出異常
let thenable = {
then(resolve) {
resolve("Resolving");
throw new Error("Throwing");
}
}
let p3 = Promise.resolve(thenable);
p3
.then(res => {
console.log(res) // Resolving
})
.catch(err => {
console.log(err)
})
let thenable = {
then(resolve) {
resolve("Resolving");
throw new Error("Throwing");
}
}
let thenable = {
then(resolve, reject) {
reject("Oops")
}
}
let p4 = Promise.resolve(thenable);
p4
.then(res => {
console.log(res)
})
.catch(err => {
console.log(err) // oops
})
- resolve異步解析Promise對象
let p1 = Promise.resolve( 1 );
let p2 = Promise.resolve( p1 );
let p3 = new Promise(function(resolve, reject){
resolve(1);
});
let p4 = new Promise(function(resolve, reject){
resolve(p1);
});
console.log(p1 === p2); // true
console.log(p1 === p3); // false
console.log(p1 === p4); // false
console.log(p3 === p4); // false
p4.then(function(value){
console.log('p4=' + value);
});
p2.then(function(value){
console.log('p2=' + value);
})
p1.then(function(value){
console.log('p1=' + value);
})
輸出結(jié)果:
true
false
false
false
p2=1
p1=1
p4=1
解釋:Promise.resolve()中的參數(shù)可以是一個普通值或者一個Promise對象,如果是普通值,則它返回一個Promise對象,PromiseValue就是該值;如果參數(shù)是Promise對象,那么直接返回這個Promise對象參數(shù)。所以p1==p2。但是因為通過new的方式創(chuàng)建的Promise對象都是一個新的對象,所以其余都是false。
為什么p4的then最先調(diào)用,但在控制臺上是最后輸出結(jié)果的呢?因為p4的resolve中接收的參數(shù)是一個Promise對象p1,resolve會對p1”提取“,獲取p1的狀態(tài)和值,但這個過程是異步的。也就是說Promise中的resolve方法會對接收的Promise對象進行“分解”。
八、resolve vs reject
var p1 = new Promise(function(resolve, reject){
resolve(Promise.resolve('resolve'));
});
var p2 = new Promise(function(resolve, reject){
resolve(Promise.reject('reject'));// 拆箱獲取值 reject
});
var p3 = new Promise(function(resolve, reject){
reject(Promise.resolve('resolve'));
});
p1.then(
function (value){
console.log('p1', value); // p1 resolve
console.log('p1 fulfilled: ' + value); //p1 fulfilled resolve
},
function (err){
console.log('p1 rejected: ' + err);
}
);
p2.then(
function (value){
console.log('p2', value)
console.log('p2 fulfilled: ' + value);
},
function (err){
console.log('p2 err', err) // p2 err reject
console.log('p2 rejected: ' + err); //p2 fulfilled reject
}
);
p3.then(
function (value){
console.log('p3', value)
console.log('p3 fulfilled: ' + value);
},
function (err){
console.log(err) // Promise {[[PromiseStatus]]: "resolved", [[PromiseValue]]: "resolve"}
console.log('p3 rejected: ' + err); // rejected [object Promise]
}
);
setTimeout(function() {
console.log(p1)
console.log(p2)
console.log(p3)
}, 100);
輸出結(jié)果:
Promise {[[PromiseStatus]]: "resolved", [[PromiseValue]]: "resolve"}
p3 rejected: [object Promise]
p1 resolve
p1 fulfilled: resolve
p2 err reject
p2 rejected: reject
Promise {[[PromiseStatus]]: "resolved", [[PromiseValue]]: "resolve"}
{[[PromiseStatus]]: "rejected", [[PromiseValue]]: "reject"}
{[[PromiseStatus]]: "rejected", [[PromiseValue]]: Promise}
解釋:
Promise回調(diào)中的第一個參數(shù)resolve,會對Promise執(zhí)行"提取"。即當resolve的參數(shù)是一個Promise對象時,resolve會"提取"獲取這個Promise對象的狀態(tài)和值,(原封不動的返回傳入的promise對象)但這個過程是異步的。
p1"提取"后,獲取到Promise對象的狀態(tài)是resolved,因此p1.then()中的第一個回調(diào)被執(zhí)行;p2"提取"后,獲取到Promise對象的狀態(tài)是rejected,因此p2.then()中的第二個回調(diào)被執(zhí)行。
但Promise回調(diào)函數(shù)中的第二個參數(shù)reject不具備”提取“的能力,reject的參數(shù)會直接傳遞給then方法中的第二個回調(diào)。因此,即使p3 reject接收了一個resolved狀態(tài)的Promise,then方法中被調(diào)用的依然是rejected,并且參數(shù)就是reject接收到的Promise對象。
參考: MDN
阮一峰 Promise