使用Promise可以表達一連串嵌套的異步操作。
每個Promise實例代表一個異步操作。
~~~
var p = new Promise(function (resolve) {
setTimeout(function () {
resolve({});
}, 100);
});
p.then(function (data) {
});
~~~
參數resolve將接受一個函數,該函數用于異步回調時傳遞結果。
然后Promise的實例使用then函數,便可以處理resolve傳遞過來的結果。
then可以鏈式操作,增強Promise的表達能力。每次執行then部署的任務``相當于拋出一個新的Promise實例。
因此可以優雅地表達在異步操作中拋出異步操作。
~~~
p.then(function (data) {
//p返回結果后執行
return new Promise(p1);
}).then(function(data){
//p1返回結果后執行
});
~~~
Promise有一套實現標準和使用方法,具體此處不再詳述。下文用ES代碼實現Promise這個輪子。
# 實現的范圍
Promise對象:
* Promise.resolve(value)//接受一個異步結果,返回一個成功的Promise實例。
* Promise.reject(reason)//接受一個異步錯誤,返回一個失敗的Promise實例。
Promise實例:
* Promise.prototype.then(onSuccess,onFailure)//接受兩個參數,配置異步成功和失敗時的處理函數,返回一個新的Promise實例。
* Promise.prototype.catch(onFailure)//接受一個參數,配置異步失敗時的處理函數,返回一個新的Promise實例。
# 代碼骨架
~~~
(function () {
"use strict";
//狀態枚舉
var emStatus = { pending: 0, resolved: 1, rejected: 2 };
var Promise = function (task) {
this.then = function (onSuccess, onFailure) {
};
this.catch = function (onFailure) {
};
var status = emStatus.pending,//任務的狀態
boxValue,//任務的結果
chain = { resolve, reject };//拋出的Promise實例的觸發裝置
var resolve = function (value) {
};
var reject = function (reason) {
};
//給異步(同步)操作注入resolve和reject
task(resolve, reject);
};
Promise.resolve = function (value) {
return new Promise(function (resolve) {
resolve(value);
});
};
Promise.reject = function (reason) {
return new Promise(function (resolve, reject) {
reject(reason);
});
};
return Promise;
})();
~~~
then和catch所部署的函數的觸發,取決于Promise實例的內部狀態。所以then和catch寫進了Promise實例的構造函數,以引用包含Promise實例內部狀態的環境。
如此一來,Promise實例只能被創建時注入的resolve和reject函數所觸發。這種具有內部封閉性的對象才能稱得上是真正的對象。
Promise實例我設置了三個狀態變量。
* status為任務的狀態,有三個,未開始(pending),成功(resolved)和失敗(rejected)。
* boxValue為任務的結果,成功失敗的結果都會被保存。
* 因為then會拋出一個新的Promise實例,所以需要保存該實例的觸發裝置,使用chain保存resolve和reject觸發裝置。
Promise.resolve/Promise.reject如意義上的拋出一個新的成功/失敗Promise實例。
# 骨架的優化和改進。
catch函數實際上相當于then(null,onFailure)。因此不用每次在構造函數中構造,只需要使用原型鏈的方式訪問。
同一個異步Promise實例的then函數可以被觸發多次,因此chain應該是個數組。
~~~
(function () {
"use strict";
var emStatus = { pending: 0, resolved: 1, rejected: 2 };
var Promise = function (task) {
this.then = function (onSuccess, onFailure) {
};
var status = emStatus.pending,
boxValue,
chainArr = [];
var resolve = function (value) {
};
var reject = function (reason) {
};
task(resolve, reject);
};
Promise.prototype = {
catch: function (onFailure) {
return this.then(null, onFailure);
}
};
Promise.resolve = function (value) {
return new Promise(function (resolve) {
resolve(value);
});
};
Promise.reject = function (reason) {
return new Promise(function (resolve, reject) {
reject(reason);
});
};
return Promise;
})();
~~~
此時只要實現then,resolve和reject。
# then
Promise實例使用then的時候,他可能有三種狀態。仍未觸發回調的pending,已經有結果的resolved和rejected。
~~~
this.then = function (onSuccess, onFailure) {
switch (status) {
case emStatus.pending:
return new Promise(function (resolve, reject) {
var chain = {};
if (onSuccess) {
}
else {
chain.resolve = resolve;
}
if (onFailure) {
}
else {
chain.reject = reject;
}
chainArr.push(chain);
});
case emStatus.resolved:
if (onSuccess) {
}
else {
return Promise.resolve(boxValue);
}
case emStatus.rejected:
if (onFailure) {
}
else {
return Promise.reject(boxValue);
}
}
};
~~~
## pending
如果then沒有傳入參數,即沒有部署操作結果的處理函數。那么返回的Promise實例將繼承之前操作的結果。
如果pending狀態時部署了處理函數,那么在未來,將會用該函數對操作返回結果進行處理。被處理后的結果,將是一個resolved的結果。期間發生異常,將被判斷為rejected。
這個處理函數包括操作成功和失敗時的處理函數:
~~~
var result;
try {
result = on(value);
}
catch (e) {
reject(e);
return;
}
if (result instanceof Promise) {
result.then(function (value) {
resolve(value);
});
}
else {
resolve(result);
}
~~~
如果處理函數接著拋出一個Promise實例,那么需要等該Promise實例觸發回調后才把處理函數視為返回了結果。
以上就是處理函數的內容,那么我們需要再將其抽象為下一個Promise的觸發裝置。
而且,因為處理函數包括成功和失敗的處理函數,所以可以抽象一個通用的模板。我給他取名nextPromise。
~~~
var nextPromise = function (on, resolve, reject) {
return function (value) {
var result;
try {
result = on(value);
}
catch (e) {
reject(e);
return;
}
if (result instanceof Promise) {
result.then(function (value) {
resolve(value);
});
}
else {
resolve(result);
}
};
};
~~~
使用該模板,就能生成下一個Promise的觸發裝置。
~~~
chain.resolve = nextPromise(onSuccess, resolve, reject);
~~~
## resolved和rejected
Promise實例完成狀態的then,情況比較簡單。只要保證返回結果一定是Promise就可以了,我給他取名getPromise。
~~~
var getPromise = function (on, value) {
var result;
try {
result = on(value);
}
catch (e) {
return Promise.reject(e);
}
if (result instanceof Promise) {
return result;
}
else {
return Promise.resolve(result);
}
};
~~~
## then
所以then的最終代碼是
~~~
this.then = function (onSuccess, onFailure) {
switch (status) {
case emStatus.pending:
return new Promise(function (resolve, reject) {
var chain = {};
if (onSuccess) {
chain.resolve = nextPromise(onSuccess, resolve, reject);
}
else {
chain.resolve = resolve;
}
if (onFailure) {
chain.reject = nextPromise(onFailure, resolve, reject);
}
else {
chain.reject = reject;
}
chainArr.push(chain);
});
case emStatus.resolved:
if (onSuccess) {
return getPromise(onSuccess, boxValue);
}
else {
return Promise.resolve(boxValue);
}
case emStatus.rejected:
if (onFailure) {
return getPromise(onFailure, boxValue);
}
else {
return Promise.reject(boxValue);
}
}
};
~~~
# resolve和reject
一個Promise實例的狀態只會被改變一次,再要改變的話就拋出異常吧。
改變Promise實例的狀態后,就是觸發之前所有使用then拋出的新Promise實例。
~~~
var resolve = function (value) {
if (status === emStatus.pending) {
status = emStatus.resolved;
boxValue = value;
chainArr.forEach(function (chain) {
chain.resolve(value);
});
}
else {
throw value;
}
};
~~~
實際上,reject跟resolve做同一樣的事情。
~~~
var reject = function (reason) {
if (status === emStatus.pending) {
status = emStatus.rejected;
boxValue = reason;
chainArr.forEach(function (chain) {
chain.reject(reason);
});
}
else {
throw reason;
}
};
~~~
# 最終代碼
~~~
(function () {
"use strict";
var emStatus = { pending: 0, resolved: 1, rejected: 2 };
var Promise = function (task) {
this.then = function (onSuccess, onFailure) {
switch (status) {
case emStatus.pending:
return new Promise(function (resolve, reject) {
var chain = {};
if (onSuccess) {
chain.resolve = nextPromise(onSuccess, resolve, reject);
}
else {
chain.resolve = resolve;
}
if (onFailure) {
chain.reject = nextPromise(onFailure, resolve, reject);
}
else {
chain.reject = reject;
}
chainArr.push(chain);
});
case emStatus.resolved:
if (onSuccess) {
return getPromise(onSuccess, boxValue);
}
else {
return Promise.resolve(boxValue);
}
case emStatus.rejected:
if (onFailure) {
return getPromise(onFailure, boxValue);
}
else {
return Promise.reject(boxValue);
}
}
};
var status = emStatus.pending,
boxValue,
chainArr = [];
var resolve = function (value) {
if (status === emStatus.pending) {
status = emStatus.resolved;
boxValue = value;
chainArr.forEach(function (chain) {
chain.resolve(value);
});
}
else {
throw value;
}
};
var reject = function (reason) {
if (status === emStatus.pending) {
status = emStatus.rejected;
boxValue = reason;
chainArr.forEach(function (chain) {
chain.reject(reason);
});
}
else {
throw reason;
}
};
try {
task(resolve, reject);
}
catch (e) {
reject(e);
}
};
Promise.prototype = {
catch: function (onFailure) {
return this.then(null, onFailure);
}
};
Promise.resolve = function (value) {
return new Promise(function (resolve) {
resolve(value);
});
};
Promise.reject = function (reason) {
return new Promise(function (resolve, reject) {
reject(reason);
});
};
var nextPromise = function (on, resolve, reject) {
return function (value) {
var result;
try {
result = on(value);
}
catch (e) {
reject(e);
return;
}
if (result instanceof Promise) {
result.then(function (value) {
resolve(value);
});
}
else {
resolve(result);
}
};
};
var getPromise = function (on, value) {
var result;
try {
result = on(value);
}
catch (e) {
return Promise.reject(e);
}
if (result instanceof Promise) {
return result;
}
else {
return Promise.resolve(result);
}
};
return Promise;
})();
~~~