本文采用es6語法實現Promise基本的功能, 適合有javascript和es6基礎的讀者,如果沒有,請閱讀
- http://es6.ruanyifeng.com/
回撥函式
在日常開發中,通常會使用ajax請求資料,拿到資料後,對資料進行處理。
但是,假設你需要多次ajax請求資料,並且每次ajax請求資料都是基於上一次請求資料作為引數,再次發出請求,於是程式碼可能就成了這樣:
function dataAjax() {
$.ajax({
url: `/woCms/getData`,
type: `get`,
success: function (data) {
reChargeAjax(data);
}
});
}
function reChargeAjax(data) {
$.ajax({
url: `/woCms/reCharge`,
type: `post`,
data: { data },
success: function (data) {
loginAjax(data);
}
});
}
....
複製程式碼
很明顯,這樣的寫法有一些缺點:
- 如果需求存在多個操作並且層層依賴的話,那這樣的巢狀就可能存在多層,並且每次都需要對返回的資料進行處理,這樣就嚴重加大了除錯難度,程式碼不是很直觀,並且增加了後期維護的難度
- 這種函式的層層巢狀,又稱之為回撥地域,為了解決這種形式存在的缺點,並支援多個併發的請求,於是Promise就出現了
Promise是什麼
- Promise是一種非同步流程的控制手段。
- Promise物件能夠使我們更合理、更規範的處理非同步流程操作.
Promise基本用法
let pro = new Promise(function (resolve, reject) {
});
複製程式碼
- Promise是一個全域性物件,也就是一個類
- 可通過new關鍵詞建立一個Promise例項
- Promise建構函式傳遞一個匿名函式, 匿名函式有2個引數: resolve和reject
- 兩個引數都是方法,resolve方法處理非同步成功後的結果
- reject處理非同步操作失敗後的原因
Promise的三種狀態
- pending: Promise 建立完成後,預設的初始化狀態
- fulfilled: resolve方法呼叫時,表示操作成功
- rejected:reject方法呼叫時,表示操作失敗
狀態只能從初始化 -> 成功或者初始化 -> 失敗,不能逆向轉換,也不能在成功fulfilled 和失敗rejected之間轉換。
Promise 類構造
好了,目前我們知道了Promise的基礎定義和語法,那麼我們就用程式碼來模擬Promise的建構函式和內部實現吧
class Promise {
// 建構函式,構造時傳入一個回撥函式
constructor(executor) {
this.status = "pending";// promise初始化狀態為pending
this.value = undefined;// 成功狀態函式的資料
this.reason = undefined;// 失敗狀態的原因
// new Promise((resolve,reject)=>{});
let resolve = data => {
// 成功狀態函式,只有當promise的狀態為pending時才能修改狀態
// 成功或者失敗狀態函式,只能呼叫其中的一個
if (this.status === "pending") {
this.status = "fulfilled";
this.value = data;
}
}
let reject = err => {
if (this.status === "pending") {
this.status = "rejected";
this.reason = err;
}
}
}
executor(resolve, rejcte);
}
複製程式碼
在Promise的匿名函式中傳入resolve, rejcte這兩個函式,分別對應著成功和失敗時的處理,根據Promise規範,只有當Promise的狀態是初始化狀態是才能修改其狀態為成功或者失敗,並且只能轉為成功或者失敗。
then方法
在瞭解Promise建立和狀態之後,我們來學習Promise中一個非常重要的方法:then方法
then()方法:用於處理操作後的程式,那麼它的語法是什麼樣子的呢,我們一起來看下
pro.then(function (res) {
//操作成功的處理
}, function (error) {
//操作失敗的處理
});
複製程式碼
那麼then函式該如何定義呢?
很明顯,then屬於Promise原型上的方法,接收2個匿名函式作為引數,第一個是成功函式,第二個是失敗函式。
也就是說當Promise狀態變為成功的時候,執行第一個函式。當狀態變為失敗的時候,執行第二個函式。因此then函式可以這樣定義:
then(onFulFiled, onRejected) {
// promise 執行返回的狀態為成功態
if (this.status === "fulfilled") {
// promise執行成功時返回的資料為成功態函式的引數
onFulFiled(this.value);
}
if (this.status === "rejected") {
// promise 執行失敗是返回的原因為失敗態函式的引數
onRejected(this.reason);
}
}
複製程式碼
Promise then 非同步程式碼佇列執行
但是我們現在來看這樣一段Promise程式碼:
let pro = new Promise((resolve, reject) => {
setTimeout(function () {
resolve("suc");
}, 1000);
});
// 同一個promise例項多次呼叫then函式
pro.then(data => {
console.log("date", data);
}, err => {
console.log("err", err);
});
pro.then(data => {
console.log("date", data);
}, err => {
console.log("err", err);
})
輸出結果為
date suc
date suc
複製程式碼
這樣Promise中非同步呼叫成功或者失敗狀態函式,並且Promise例項多次呼叫then方法,我們可以看到最後輸出多次成功狀態函式中的內容,而此時:
- Promise中非同步執行狀態函式,但Promise的then函式是還是pending狀態
- Promise例項pro多次呼叫then函式,當狀態是pending的時候依然會執行狀態函式
那麼這塊在then函式中是如何處理pending狀態的邏輯呢?
採用釋出訂閱的方式,將狀態函式存到佇列中,之後呼叫時取出。
class Promise{
constructor(executor){
// 當promise中出現非同步程式碼將成功態或者失敗態函式封裝時
// 採用釋出訂閱的方式,將狀態函式存到佇列中,之後呼叫時取出
this.onFuiFiledAry = [];
this.onRejectedAry = [];
let resolve = data => {
if (this.status === "pending") {
...
// 當狀態函式佇列中有存放的函式時,取出並執行,佇列裡面存的都是函式
this.onFulFiledAry.forEach(fn => fn());
}
}
let reject = err => {
if (this.status === "pending") {
...
this.onRejectedAry.forEach(fn => fn());
}
}
}
then(onFuiFiled, onRejected) {
...
if (this.status === "pending") {
this.onFuiFiledAry.push(() => {
onFuiFiled(this.value);
});
this.onRejectedAry.push(() => {
onRejected(this.reason);
});
}
}
}
複製程式碼
Promise then 函式返回一個新的Promise
看過jQuery原始碼的童鞋肯定都清楚,jQuery是如何實現鏈式呼叫的呢?對,就是this,jQuery通過在函式執行完成後通過返回當前物件的引用,也就是this來實現鏈式呼叫。
我們先看看一個例子,假設Promise用this來實現鏈式呼叫,會出現什麼情況呢?
let pro = new Promise(function (resolve, reject) {
resolve(123);
});
let p2 = pro.then(function () {
// 如果返回this,那p和p2是同一個promise的話,此時p2的狀態也應該是成功
// p2狀態設定為成功態了,就不能再修改了,但是此時丟擲了異常,應該是走下個then的成功態函式
throw new Error("error");
});
p2.then(function (data) {
console.log("promise success", data);
}, function (err) {
// 此時捕獲到了錯誤,說明不是同一個promise,因為promise的狀態變為成功後是不能再修改狀態
console.log("promise or this:", err);
})
複製程式碼
很明顯,Promise發生異常,丟擲Error時,p2例項的狀態已經是失敗態了,所以會走下一個then的失敗態函式,而結果也正是這樣,說明Promise並不是通過this來實現鏈式呼叫。
那Promise中的鏈式呼叫是如何實現的呢?
結果是,返回一個新的Promise.
then(onFuiFiled, onRejected) {
let newpro;
if (this.status === "fulfilled") {
newpro = new Promise((resolve, reject) => {
onFuiFiled(this.value);
});
}
if (this.status === "rejected") {
newpro = new Promise((resolve, reject) => {
onRejected(this.reason);
});
}
if (this.status === "pending") {
newpro = new Promise((resolve, reject) => {
this.onFuiFiledAry.push(() => {
onFuiFiled(this.value);
});
this.onRejectedAry.push(() => {
onRejected(this.reason);
});
});
}
return newpro;
}
複製程式碼
Promise then函式返回值解析
好了,我們繼續將目光放在then函式上,then函式接收兩個匿名函式,那假設then函式返回的是數值、物件、函式,或者是promise,這塊Promise又是如何實現的呢?
來,我們先看例子:
let pro = new Promise((resolve, reject) => {
resolve(123);
});
pro.then(data => {
console.log("then-1",data);
return 1;
}).then(data => {
console.log("then-2", data);
});
複製程式碼
例子輸出的結果是
then-1 123
then-2 1
也就是說Promise會根據then狀態函式執行時返回的不同的結果來進行解析:
- 如果上一個then函式返回的是數值、物件、函式的話,是會直接將這個數值和物件直接返回給下個then;
- 如果then返回的是null,下個then獲取到的也是null;
- 如果then返回的是一個新的promise的話,則根據新的promise的狀態函式來確定下個then呼叫哪個狀態函式,如果返回的新的promise沒有執行任何狀態函式的話,則這個promise的狀態是pending
那這塊,我們該如何實現呢?來,看具體程式碼吧
function analysisPromise(promise, res, resolve, reject) {
// promise 中返回的是promise例項本身,並沒有呼叫任何的狀態函式方法
if (promise === res) {
return reject(new TypeError("Recycle"));
}
// res 不是null,res是物件或者是函式
if (res !== null && (typeof res === "object" || typeof res === "function")) {
try {
let then = res.then;//防止使用Object.defineProperty定義成{then:{}}
if (typeof then === "function") {//此時當做Promise在進行解析
then.call(res, y => {
// y作為引數,promise中成功態的data,遞迴呼叫函式進行處理
analysisPromise(promise, y, resolve, reject);
}, err => {
reject(err);
})
} else {
// 此處then是普通物件,則直接呼叫下個then的成功態函式並被當做引數輸出
resolve(res);
}
} catch (error) {
reject(error);
}
} else {
// res 是數值
resolve(res);
}
}
then(onFuiFiled, onRejected) {
let newpro;
if (this.status === "fulfilled") {
newpro = new Promise((resolve, reject) => {
let res = onFuiFiled(this.value);
analysisPromise(newpro, res, resolve, reject);
});
}
...
return newpro;
}
複製程式碼
analysisPromise函式就是用來解析Promise中then函式的返回值的,在呼叫then函式中的狀態函式返回結果值時都必須要進行處理。
Promise中的then函式是非同步
現在,我們再來看Promise的一個例子:
let pro = new Promise((resolve, reject) => {
resolve(123);
});
pro.then(data => {
console.log(1);
});
console.log(2);
複製程式碼
輸出的結果是
2
1
so,這裡先輸出的是2,而then函式中的狀態函式後輸出1,說明同步程式碼先於then函式的非同步程式碼執行
那這部分的程式碼我們該如何來實現呢?
採用setTimeout函式,給then函式中的每個匿名函式都加上setTimeout函式程式碼,就比如這樣:
then(onFuiFiled, onRejected) {
let newpro;
if (this.status === "fulfilled") {
newpro = new Promise((resolve, reject) => {
setTimeout(() => {
try {
let res = onFuiFiled(this.value);
analysisPromise(newpro, res, resolve, reject);
} catch (error) {
reject(error);
}
});
});
}
...
return newpro;
}
複製程式碼
好了,關於Promise的大致實現就先分析到這裡,希望對大家有幫助,文章中如有錯誤,歡迎指正
文章主要參考一下具體學習資源
- http://es6.ruanyifeng.com/
- http://liubin.org/promises-book/#es6-promises