Promise是ES6中新增的一個類,專門用來解決異步回調地獄的問題,異步代碼同步顯示出來。
回調函數的例子
//成功回調函數
function successCallback() { // 固定格式
console.log('SUCCESS')
}
//失敗回調函數
function failureCallback() { // 固定格式
console.log('FAILURE')
}
function myFunc(param, success, failure) {
if (param) {
success()
} else {
failure()
}
}
myFunc(0, successCallback, failureCallback) // FAILURE
myFunc(1, successCallback, failureCallback) // SUCCESS
以上回調函數可用promise實現
var t = 0;
var promise = new Promise(function(resolve, reject) {
if (t%2 == 0) {
resolve();
} else {
reject();
}
});
promise.then(function() {
console.log('SUCCESS');
}, function(){
console.log('FAILURE');
});
運行結果:
SUCCESS
注:new了一個Promise對象,沒調用它,函數就已經執行了。所以使用Promise時一般包在一個函數中,在需要時調用。
因此,上面的實例可以包在函數fn()裏面,需要時就調用fn()
function fn() {
var t = 1;
var promise = new Promise(function(resolve, reject) {
if (t%2 == 0) {
console.log(1);
resolve();
console.log(1.1);
} else {
console.log(2);
reject();
console.log(2.1);
}
});
promise.then(function() {
console.log(3);
console.log('SUCCESS');
console.log(3.1);
}, function(){
console.log(4);
console.log('FAILURE');
console.log(4.1);
});
return promise;
}
fn();
運行結果:
2
2.1
4
FAILURE
4.1
Promise運行流程
function test_01() {
new Promise(function(resolve, reject) {
setTimeout(() => {
resolve('success....'),
reject('fail...')
}, 1000);
}).then(
value => {
console.log('onResolved()1', value);
}
).catch(
reason => {
console.log('onRejected()1', reason);
}
)
}
test_01();
輸出結果:onResolved()1 success....
setTimeout中resolve/reject只能執行一個,要麼成功、要麼失敗。
function test_02() {
// 產生一個成功值爲1的promise對象
const p1 = new Promise(function(resolve, reject) {
resolve(1)
})
// 產生一個成功值爲2的promise對象
const p2 = Promise.resolve(2);
// 產生一個失敗值爲3的promise對象
const p3 = Promise.reject(3);
p1.then(value => {console.log(value)})
p2.then(value => {console.log(value)})
// p3.then(null, reason => {console.log(reason)}) // 寫法1
// p3.then(null, value => {console.log(value)}) // 寫法2
// p3.catch(value => {console.log(value)}) // 寫法3
p3.catch(reason => {console.log(reason)}) // 寫法4
}
test_02();
輸出結果:1 2 3
function test_01() {
new Promise(resolve => {
console.log(123);
}).then(value => {console.log(456)})
console.log("hi")
}
test_01();
執行結果:123 hi
注:then沒有被執行,是因爲沒有resolve通知。只有得到通知纔會進入微任務隊列。
Promise異常捕獲
Promise.prototype.catch方法是Promise.prototype.then(null, rejection)的別名,用於指定發生錯誤時的回調函數。
Promise對象的錯誤具有“冒泡”性質,會一直向後傳遞,直到被捕獲爲止。也就是說,錯誤總是會被下一個catch語句捕獲。
getJSON("/visa.json").then(function(json) {
return json.name;
}).then(function(name) {
// proceed
}).catch(function(error) {
//處理前面任一個then函數拋出的錯誤
});
<script>
function test_01() {
const p = new Promise(function(resolve, reject) {
setTimeout(() => {
// resolve('success....'), // promise變爲resolved 成功狀態
// reject('fail...') // promise變爲rejected 失敗狀態
throw new Error('error....') // 拋出異常,promise變爲pending狀態,value爲'error...'
}, 1000);
});
console.log(p);
}
test_01();
</script>
先改狀態,還是先改值?
function test_03() { // 常規:先執行回調函數,後改狀態
new Promise((resolve, reject) => {
setTimeout(() => {
resolve(1); // 2. 改變狀態(pending → resolved, 同時調用回調函數指定數據)setTimeout爲宏任務
},1000)
}).then( // 1. 執行回調函數(微任務),保存當前指定的回調函數
value => {},
reason => {}
)
}
function test_04() {// 先改狀態,後執行回調函數
new Promise((resolve, reject) => {
resolve(1); // 1. 改變狀態(pending → resolved, 同時調用回調函數指定數據)
}).then( // 2. 後執行回調函數(微任務),異步執行回調函數
value => {},
reason => {}
)
}
Promise狀態改變是不可逆的,pending → resolved之後,無法再轉爲其他狀態。
因此,resolve / reject 只能執行一個。
Promise鏈式調用
-
回調地獄
-
代碼難閱讀
-
多個串聯函數適合使用回調函數嵌套
-
鏈式Promise是指在當前promise達到fulfilled狀態後,即開始進行下一個promise(後鄰promise)。如何銜接當前promise和後鄰promise呢?(這是這裏的難點)。只要在then方法裏面return一個promise就好了(得到一個promise是前提)。
注意: 一定要有返回值,否則,callback 將無法獲取上一個 Promise 的結果。
(如果使用箭頭函數,() => x 比 () => { return x; } 更簡潔一些,但後一種保留 return 的寫法才支持使用多個語句。)。
function runAsync1(){
var p = new Promise(function(resolve, reject){
//做一些異步操作
setTimeout(function(){
console.log('異步任務1執行完成');
resolve('隨便什麼數據1');
}, 1000);
});
return p;
}
function runAsync2(){
var p = new Promise(function(resolve, reject){
//做一些異步操作
setTimeout(function(){
console.log('異步任務2執行完成');
resolve('隨便什麼數據2');
}, 2000);
});
return p;
}
function runAsync3(){
var p = new Promise(function(resolve, reject){
//做一些異步操作
setTimeout(function(){
console.log('異步任務3執行完成');
resolve('隨便什麼數據3');
}, 2000);
});
return p;
}
runAsync1()
.then(function(data){
console.log(data);
return runAsync2();
})
.then(function(data){
console.log(data);
return runAsync3();
})
.then(function(data){
console.log(data);
});
運行結果:
異步任務1執行完成
隨便什麼數據1
異步任務2執行完成
隨便什麼數據2
異步任務3執行完成
隨便什麼數據3
如果then不返回Promise對象,則後面then不再受到數據。
function runAsync1(){
var p = new Promise(function(resolve, reject){
//做一些異步操作
setTimeout(function(){
console.log('異步任務1執行完成');
resolve('隨便什麼數據1');
}, 1000);
});
return p;
}
function runAsync2(){
var p = new Promise(function(resolve, reject){
//做一些異步操作
setTimeout(function(){
console.log('異步任務2執行完成');
resolve('隨便什麼數據2');
}, 2000);
});
return p;
}
function runAsync3(){
var p = new Promise(function(resolve, reject){
//做一些異步操作
setTimeout(function(){
console.log('異步任務3執行完成');
resolve('隨便什麼數據3');
}, 2000);
});
return p;
}
runAsync1()
.then(function(data){
console.log(data);
return runAsync2();
})
.then(function(data){
console.log(data);
return '直接返回數據'; //這裏直接返回數據
})
.then(function(data){
console.log(data);
});
運行結果:
異步任務1執行完成
隨便什麼數據1
異步任務2執行完成
隨便什麼數據2
直接返回數據
promise裏面的then函數僅僅是註冊了後續需要執行的代碼,真正的執行是在resolve方法裏面執行的,理清了這層,再來分析源碼會省力的多。
鏈接:
https://blog.csdn.net/shan1991fei/article/details/78966297 (ES6 Promise 用法)
Promise設計模式 (https://zhuanlan.zhihu.com/p/42377418 (30分鐘,讓你徹底明白Promise原理))
Promise的實現過程,其主要使用了設計模式中的觀察者模式:
1. 通過Promise.prototype.then和Promise.prototype.catch方法將觀察者方法註冊到被觀察者Promise對象中,同時返回一個新的Promise對象,以便可以鏈式調用。
2. 被觀察者管理內部pending、fulfilled和rejected的狀態轉變,同時通過構造函數中傳遞的resolve和reject方法以主動觸發狀態轉變和通知觀察者。