原文在個人博客中:原文地址 若是文章對您有幫助,您的star是對我最好的鼓勵~git
簡要介紹:Promise容許咱們經過鏈式調用的方式來解決「回調地獄」的問題,特別是在異步過程當中,經過Promise能夠保證代碼的整潔性和可讀性。本文主要解讀Promise/A+規範,並在此規範的基礎上,本身實現一個Promise.github
在瞭解Promise規範以前,咱們知道主流的高版本瀏覽器已經支持ECMA中的Promise.npm
建立一個promise實例:數組
var p=new Promise(function(resolve,reject){
setTimeout(function(){
resolve("success")
},1000);
console.log("建立一個新的promise");
})
p.then(function(x){
console.log(x)
})
//輸出:
建立一個新的promise
success
複製代碼
上述是一個promise的實例,輸出內容爲,「建立一個promise」,延遲1000ms後,輸出"success"。promise
從上述的例子能夠看出,promise方便處理異步操做。此外promise還能夠鏈式的調用:瀏覽器
var p=new Promise(function(resolve,reject){resolve()});
p.then(...).then(...).then(...)
複製代碼
此外Promise除了then方法外,還提供了Promise.resolve、Promise.all、Promise.race等等方法。bash
Promise/A+規範擴展了早期的Promise/A proposal提案,咱們來解讀一下Promise/A+規範。異步
(1)"promise"是一個對象或者函數,該對象或者函數有一個then方法async
(2)"thenable"是一個對象或者函數,用來定義then方法函數
(3)"value"是promise狀態成功時的值
(4)"reason"是promise狀態失敗時的值
咱們明確術語的目的,是爲了在本身實現promise時,保持代碼的規範性(也能夠跳過此小節)
(1)一個promise必須有3個狀態,pending,fulfilled(resolved),rejected當處於pending狀態的時候,能夠轉移到fulfilled(resolved)或者rejected狀態。當處於fulfilled(resolved)狀態或者rejected狀態的時候,就不可變。
promise英文譯爲承諾,也就是說promise的狀態一旦發生改變,就永遠是不可逆的。
(2)一個promise必須有一個then方法,then方法接受兩個參數:
promise.then(onFulfilled,onRejected)
複製代碼
其中onFulfilled方法表示狀態從pending——>fulfilled(resolved)時所執行的方法,而onRejected表示狀態從pending——>rejected所執行的方法。
(3)爲了實現鏈式調用,then方法必須返回一個promise
promise2=promise1.then(onFulfilled,onRejected)
複製代碼
解讀了Promise/A+規範以後,下面咱們來看如何實現一個Promise, 首先構造一個myPromise函數,關於全部變量和函數名,應該與規範中保持相同。
function myPromise(constructor){
let self=this;
self.status="pending" //定義狀態改變前的初始狀態
self.value=undefined;//定義狀態爲resolved的時候的狀態
self.reason=undefined;//定義狀態爲rejected的時候的狀態
function resolve(value){
//兩個==="pending",保證了狀態的改變是不可逆的
if(self.status==="pending"){
self.value=value;
self.status="resolved";
}
}
function reject(reason){
//兩個==="pending",保證了狀態的改變是不可逆的
if(self.status==="pending"){
self.reason=reason;
self.status="rejected";
}
}
//捕獲構造異常
try{
constructor(resolve,reject);
}catch(e){
reject(e);
}
}
複製代碼
同時,須要在myPromise的原型上定義鏈式調用的then方法:
myPromise.prototype.then=function(onFullfilled,onRejected){
let self=this;
switch(self.status){
case "resolved":
onFullfilled(self.value);
break;
case "rejected":
onRejected(self.reason);
break;
default:
}
}
複製代碼
上述就是一個初始版本的myPromise,在myPromise裏發生狀態改變,而後在相應的then方法裏面根據不一樣的狀態能夠執行不一樣的操做。
var p=new myPromise(function(resolve,reject){resolve(1)});
p.then(function(x){console.log(x)})
//輸出1
複製代碼
可是這裏myPromise沒法處理異步的resolve.好比:
var p=new myPromise(function(resolve,reject){setTimeout(function(){resolve(1)},1000)});
p.then(function(x){console.log(x)})
//無輸出
複製代碼
爲了處理異步resolve,咱們修改myPromise的定義,用2個數組onFullfilledArray和onRejectedArray來保存異步的方法。在狀態發生改變時,一次遍歷執行數組中的方法。
function myPromise(constructor){
let self=this;
self.status="pending" //定義狀態改變前的初始狀態
self.value=undefined;//定義狀態爲resolved的時候的狀態
self.reason=undefined;//定義狀態爲rejected的時候的狀態
self.onFullfilledArray=[];
self.onRejectedArray=[];
function resolve(value){
if(self.status==="pending"){
self.value=value;
self.status="resolved";
self.onFullfilledArray.forEach(function(f){
f(self.value);
//若是狀態從pending變爲resolved,
//那麼就遍歷執行裏面的異步方法
});
}
}
function reject(reason){
if(self.status==="pending"){
self.reason=reason;
self.status="rejected";
self.onRejectedArray.forEach(function(f){
f(self.reason);
//若是狀態從pending變爲rejected,
//那麼就遍歷執行裏面的異步方法
})
}
}
//捕獲構造異常
try{
constructor(resolve,reject);
}catch(e){
reject(e);
}
}
複製代碼
對於then方法,狀態爲pending時,往數組裏面添加方法:
myPromise.prototype.then=function(onFullfilled,onRejected){
let self=this;
switch(self.status){
case "pending":
self.onFullfilledArray.push(function(){
onFullfilled(self.value)
});
self.onRejectedArray.push(function(){
onRejected(self.reason)
});
case "resolved":
onFullfilled(self.value);
break;
case "rejected":
onRejected(self.reason);
break;
default:
}
}
複製代碼
這樣,經過兩個數組,在狀態發生改變以後再開始執行,這樣能夠處理異步resolve沒法調用的問題。這個版本的myPromise就能處理全部的異步,那麼這樣作就完整了嗎?
沒有,咱們作Promise/A+規範的最大的特色就是鏈式調用,也就是說then方法返回的應該是一個promise。
要經過then方法實現鏈式調用,那麼也就是說then方法每次調用須要返回一個primise,同時在返回promise的構造體裏面,增長錯誤處理部分,咱們來改造then方法
myPromise.prototype.then=function(onFullfilled,onRejected){
let self=this;
let promise2;
switch(self.status){
case "pending":
promise2=new myPromise(function(resolve,reject){
self.onFullfilledArray.push(function(){
try{
let temple=onFullfilled(self.value);
resolve(temple)
}catch(e){
reject(e) //error catch
}
});
self.onRejectedArray.push(function(){
try{
let temple=onRejected(self.reason);
reject(temple)
}catch(e){
reject(e)// error catch
}
});
})
case "resolved":
promise2=new myPromise(function(resolve,reject){
try{
let temple=onFullfilled(self.value);
//將上次一then裏面的方法傳遞進下一個Promise的狀態
resolve(temple);
}catch(e){
reject(e);//error catch
}
})
break;
case "rejected":
promise2=new myPromise(function(resolve,reject){
try{
let temple=onRejected(self.reason);
//將then裏面的方法傳遞到下一個Promise的狀態裏
resolve(temple);
}catch(e){
reject(e);
}
})
break;
default:
}
return promise2;
}
複製代碼
這樣經過then方法返回一個promise就能夠實現鏈式的調用:
p.then(function(x){console.log(x)}).then(function(){console.log("鏈式調用1")}).then(function(){console.log("鏈式調用2")})
//輸出
1
鏈式調用1
鏈式調用2
複製代碼
這樣咱們雖然實現了then函數的鏈式調用,可是還有一個問題,就是在Promise/A+規範中then函數裏面的onFullfilled方法和onRejected方法的返回值能夠是對象,函數,甚至是另外一個promise。
特別的爲了解決onFullfilled和onRejected方法的返回值多是一個promise的問題。
(1)首先來看promise中對於onFullfilled函數的返回值的要求
I)若是onFullfilled函數返回的是該promise自己,那麼會拋出類型錯誤
II)若是onFullfilled函數返回的是一個不一樣的promise,那麼執行該promise的then函數,在then函數裏將這個promise的狀態轉移給新的promise III)若是返回的是一個嵌套類型的promsie,那麼須要遞歸。
IV)若是返回的是非promsie的對象或者函數,那麼會選擇直接將該對象或者函數,給新的promise。
根據上述返回值的要求,咱們要從新的定義resolve函數,這裏Promise/A+規範裏面稱爲:resolvePromise函數,該函數接受當前的promise、onFullfilled函數或者onRejected函數的返回值、resolve和reject做爲參數。
下面咱們來看resolvePromise函數的定義:
function resolvePromise(promise,x,resolve,reject){
if(promise===x){
throw new TypeError("type error")
}
let isUsed;
if(x!==null&&(typeof x==="object"||typeof x==="function")){
try{
let then=x.then;
if(typeof then==="function"){
//是一個promise的狀況
then.call(x,function(y){
if(isUsed)return;
isUsed=true;
resolvePromise(promise,y,resolve,reject);
},function(e){
if(isUsed)return;
isUsed=true;
reject(e);
})
}else{
//僅僅是一個函數或者是對象
resolve(x)
}
}catch(e){
if(isUsed)return;
isUsed=true;
reject(e);
}
}else{
//返回的基本類型,直接resolve
resolve(x)
}
}
複製代碼
改變了resolvePromise函數以後,咱們在then方法裏面的調用也變成了resolvePromise而不是promise。
myPromise.prototype.then=function(onFullfilled,onRejected){
let self=this;
let promise2;
switch(self.status){
case "pending":
promise2=new myPromise(function(resolve,reject){
self.onFullfilledArray.push(function(){
setTimeout(function(){
try{
let temple=onFullfilled(self.value);
resolvePromise(temple)
}catch(e){
reject(e) //error catch
}
})
});
self.onRejectedArray.push(function(){
setTimeout(function(){
try{
let temple=onRejected(self.reason);
resolvePromise(temple)
}catch(e){
reject(e)// error catch
}
})
});
})
case "resolved":
promise2=new myPromise(function(resolve,reject){
setTimeout(function(){
try{
let temple=onFullfilled(self.value);
//將上次一then裏面的方法傳遞進下一個Promise狀態
resolvePromise(temple);
}catch(e){
reject(e);//error catch
}
})
})
break;
case "rejected":
promise2=new myPromise(function(resolve,reject){
setTimeout(function(){
try{
let temple=onRejected(self.reason);
//將then裏面的方法傳遞到下一個Promise的狀態裏
resolvePromise(temple);
}catch(e){
reject(e);
}
})
})
break;
default:
}
return promise2;
}
複製代碼
這樣就能處理onFullfilled各類返回值的狀況。
var p=new Promise(function(resolve,reject){resolve("初始化promise")})
p.then(function(){return new Promise(function(resolve,reject){resolve("then裏面的promise返回值")})}).then(function(x){console.log(x)})
//輸出
then裏面promise的返回值
複製代碼
到這裏可能有點亂,咱們再理一理,首先返回值有兩個:
then函數的返回值——>返回一個新promise,從而實現鏈式調用
then函數中的onFullfilled和onRejected方法——>返回基本值或者新的promise
這二者實際上是有關聯的,onFullfilled方法的返回值能夠決定then函數的返回值。
npm install -g promises-aplus-tests
複製代碼
具體用法請看promise test而後
promises-aplus-tests myPromise.js
複製代碼
結果爲:
完整代碼的地址
https://github.com/forthealllight/promise-achieve
interface IConstructor{
(resolve:IResolve,reject:IReject):void
}
interface IResolve {
(x:any):void
}
interface IReject {
(x:any):void
}
function myPromise(constructor:IConstructor):void{
let self:object=this;
self.status="pending";
self.value=undefined;//if pending->resolved
self.reason=undefined;//if pending->rejected
self.onFullfilledArray=[];//to deal with async(resolved)
self.onRejectedArray=[];//to deal with async(rejeced)
let resolve:IResolve;
resolve=function(value:any):void{
//pending->resolved
if(self.status==="pending"){
self.status="resolved";
self.value=value;
self.onFullfilledArray.forEach(function(f){
f(self.value);
})
}
}
let reject:IReject;
reject=function(reason:any):void{
if(self.status==="pending"){
self.status="rejected";
self.reason=reason;
self.onRejectedArray.forEach(function(f){
f(self.reason);
})
}
}
//According to the definition that the function "constructor" accept two parameters
//error catch
try {
constructor(resolve,reject);
} catch (e) {
reject(e);
}
}
複製代碼
單純的寫個工具函數,用ts仍是有點影響可讀性。