本文采用es6語法實現Promise基本的功能, 適合有javascript和es6基礎的讀者,若是沒有,請閱讀javascript
在平常開發中,一般會使用ajax請求數據,拿到數據後,對數據進行處理。java
可是,假設你須要屢次ajax請求數據,而且每次ajax請求數據都是基於上一次請求數據做爲參數,再次發出請求,因而代碼可能就成了這樣:es6
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);
}
});
}
....
複製代碼
很明顯,這樣的寫法有一些缺點:ajax
let pro = new Promise(function (resolve, reject) {
});
複製代碼
狀態只能從初始化 -> 成功或者初始化 -> 失敗,不能逆向轉換,也不能在成功fulfilled 和失敗rejected之間轉換。promise
好了,目前咱們知道了Promise的基礎定義和語法,那麼咱們就用代碼來模擬Promise的構造函數和內部實現吧bash
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的狀態是初始化狀態是才能修改其狀態爲成功或者失敗,而且只能轉爲成功或者失敗。併發
在瞭解Promise建立和狀態以後,咱們來學習Promise中一個很是重要的方法:then方法異步
then()方法:用於處理操做後的程序,那麼它的語法是什麼樣子的呢,咱們一塊兒來看下函數
pro.then(function (res) {
//操做成功的處理
}, function (error) {
//操做失敗的處理
});
複製代碼
那麼then函數該如何定義呢?post
很明顯,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代碼:
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方法,咱們能夠看到最後輸出屢次成功狀態函數中的內容,而此時:
那麼這塊在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);
});
}
}
}
複製代碼
看過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;
}
複製代碼
好了,咱們繼續將目光放在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狀態函數執行時返回的不一樣的結果來進行解析:
那這塊,咱們該如何實現呢?來,看具體代碼吧
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的一個例子:
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的大體實現就先分析到這裏,但願對你們有幫助,文章中若有錯誤,歡迎指正
文章主要參考一下具體學習資源