在前端的日常工作中,回撥函式(callback)應該是見怪不怪了,但是當回撥函式遇上了非同步(async),這就令人髮指了。那麼非同步是什麼意思呢,簡單地說就是不等你執行完,就先執行下方的程式碼了。
舉個?:
我們最常用的非同步操作應該是ajax了(想當初我第一次用ajax的時候,簡直就是災難。明明資源載入成功了,怎麼就是沒有調到資源中的資料呢?真令人頭大啊。),只能等待載入完畢,再執行相關操作才能成功。因此我們看到的程式碼應該都是這樣的。
/**
@param callback 回撥函式
*/
function getData(url,callback){
$.ajax({
url:url,
success:function(result){
callback(result);
}
});
}
//假設我有好多個ajax,每個ajax都需要上一個ajax的支援,於是……地獄出現了……
getData(url1,function(res){
getData(url2,function(res){
getData(url3,function(res){
//終於可以幹正事了
})
})
})
複製程式碼
朋友們,回撥地獄(callback Hell)瞭解下。
於是promise出現了,他的出現就是解決了回撥地獄!他對非同步的函式進行了封裝,把回撥變成了鏈式呼叫。
舉個?:
function getData(url){
return new Promise((resolve,reject)=>{
$.ajax({
url:url,
success:function(result){
resolve(result);
},
error:function(error){
reject(error);
}
});
})
}
getData(url1).then(function(res){
return getData(url2)
}).then(function(res){
return getData(url3)
}).then(function(res){
//幹正事啦!
})
複製程式碼
確實。簡潔了不少,至少不會被裡三層外三層的括號弄暈。
但是當初我聽到promise的時候,我內心是拒絕的。雖然內心拒絕,但是該來的還是要來的,該學的還是要學的,畢竟時代在進步,與時俱進還是很必要的!那麼這個promise是怎麼實現的呢???
實現一:狀態
小夥伴們,這裡promise可不是男女約會中浪漫的臺詞 ”I promise XXX“ ,而是一種規範,點選此處獲取規範。不過這裡的promise和現實生活中的promise一樣,都有實現(fulfilled),拒絕(rejected)和等待(pending)這三種狀態。
舉個?:
假定 Mary 和 Mike 是一對情侶,半年前,Mike 向 Mary 承諾(promise)半年內完成他們的婚禮,但是直到現在 Mike 也沒有做出行動,因此 Mary 表示她不會一直等待(pending)下去,於是他們分手了,那麼這個承諾(promise)就是作廢了(rejected)。如果這半年內 Mike 和 Mary 結了婚,那麼現在 Mike 應該已經實現(fulfilled)了他對 Mary 的承諾(promise)。
所以說,所有的promise都有一個結果狀態——實現(fulfilled)或者拒絕(rejected),而結果出來之前的狀態就是等待(pending)。
//p1.js
function Promise(executor){
let _=this;
_.value=undefined;
_.reason=undefined;
_.state="pending"//大家一開始都是一樣,等著吧
function resolve(value){
_.value=value//實現之後的感言
_.state="fulfilled"//實現啦!
}
function reject(reason){
_.reason=reason //給我一個被拒絕的理由
_.state="rejected" //被拒絕了!
}
executor(resolve,reject)
}
//e.g
let Iagree=new Promise((resolve,reject)=>{
resolve("我開心就同意了");//
})
let Idisagree=new Promise((resolve,reject)=>{
reject("我不開心就拒絕了");
})
let noResult=new Promise((resolve,reject)=>{
})
console.log(Iagree.state,Idisagree.state,noResult.state)
複製程式碼
實現二:新增then函式
不過我只知道一個狀態有何用?我還要進行下一步噠!我們需要一個then
,用於進行下一步的操作。
//p2.js
Promise.prototype.then=function(onFulfilled, onRejected){
let _=this;
if(_.state=="pending"){}
if(_.state=="fulfilled"){
onFulfilled(_.value)
}
if(_.state=="rejected"){
onRejected(_.reason)
}
}
//e.g
let Iagree=new Promise((resolve,reject)=>{
resolve("我開心就同意了");//強行完成(fullfilled)
})
Iagree.then((data)=>{
console.log(Iagree.state)
},(e)=>{
console.log(e)
})
複製程式碼
實現三:實現非同步執行
不過這個都是同時進行,不是非同步的。我們來瞅一眼非同步~
這個時候我們需要把回撥函式丟到resolve或者reject中,但是如果我們的後續方法很多呢?then好多次怎麼辦!將回撥丟到的佇列中,到時候Foreach一下逐個執行。
//p3.js
function Promise(executor){
//....
_.resolveCallbacks=[];//callbacks在pending中新增,fullfilled中執行
_.rejectCallbacks=[];//callbacks在pending中新增,rejected中執行
function resolve(value){
//....
_.resolveCallbacks.forEach((fn)=>fn())
}
function reject(reason){
//....
_.rejectCallbacks.forEach((fn)=>fn())
}
//....
}
Promise.prototype.then=function(onFulfilled, onRejected){
let _=this;
if(_.state=="pending"){
//把回撥方法塞進佇列中
_.resolveCallbacks.push(()=>{
onFulfilled(_.value)
})
_.rejectCallbacks.push(()=>{
onRejected(_.reason)
})
}
//....
}
//e.g
let Iagree=new Promise((resolve,reject)=>{
setTimeout(()=>{
resolve("我開心就同意了");
},1000)
})
//為了防止多次then,所以回撥方法需要丟入佇列中,防止方法被覆蓋。
Iagree.then((data)=>{
console.log(Iagree.state)
},(e)=>{
console.log(e)
})
Iagree.then((data)=>{
console.log(Iagree.state+1)
},(e)=>{
console.log(e)
})
複製程式碼
實現四:實現鏈式呼叫
那麼問題來了,如果我直接then,可不可以?像這這樣:
Iagree.then((data)=>{
...
}).then((data)=>{
...
}).then((data)=>{
...
})
複製程式碼
如果想要這樣寫,那麼上一步的then
必須返回一個promise物件才可以,不然哪裡變出一個then
方法。因此我們需要在then
中new
一個新的promise,用於下一個鏈式呼叫的then
。
//p4.js
function resolvePromise(promise,x,resolve,reject){
//如果x可能是一個promise
if(x!==null&&(typeof x==="object"||typeof x==="function")){
let then=x.then;
//如果x是一個promise,因為promise都要有then函式的
if(typeof then === "function"){
//y表示x這個promise的值
then.call(x,y=>{
//繼續遍歷,直至返回值不是promise
resolvePromise(promise,y,resolve,reject)
},err=>{
reject(err)
})
}else{
//如果x是個普通物件,直接執行
resolve(x)
}
}else{
//如果x不是一個promise,也就是x是一個常量,直接執行
resolve(x)
}
}
Promise.prototype.then=function(onFulfilled, onRejected){
let _=this;
let promise2;
//將當前promise的值傳遞到下一次then的呼叫中
function resolveFunction(promise,resolve,reject){
let x=onFulfilled(_.value)
resolvePromise(promise,x,resolve,reject)
}
function rejectFunction(promise,resolve,reject){
let x=onRejected(_.reason)
resolvePromise(promise,x,resolve,reject)
}
promise2=new Promise((resolve,reject)=>{
if(_.state=="pending"){
//把回撥方法塞進佇列中
_.resolveCallbacks.push(()=>{
resolveFunction(promise2,resolve,reject)
})
_.rejectCallbacks.push(()=>{
rejectFunction(promise2,resolve,reject)
})
}
if(_.state=="fulfilled"){
resolveFunction(promise2,resolve,reject)
}
if(_.state=="rejected"){
rejectFunction(promise2,resolve,reject)
}
})
return promise2
}
//e.g
let Iagree=new Promise((resolve,reject)=>{
setTimeout(()=>{
resolve("我開心就同意了");
},1000)
})
//為了防止多次then,所以回撥方法需要丟入佇列中,防止方法被覆蓋。
Iagree.then((data)=>{
console.log(data)
return new Promise((resolve,reject)=>{
setTimeout(()=>{
resolve("看心情幹活");
},1000)
})
}).then((data)=>{
console.log("前方返回一個promise:"+data)
return data+",我是一個常量"
}).then((data)=>{
console.log("常量返回:"+data)
}).then((data)=>{
console.log("前方無法返回:"+data)
})
複製程式碼
這樣我們就可以愉快地用鏈式呼叫promise了,想想就美滋滋。 不過以上只是簡單粗暴的實現promise的方式,只是一個原理,還有promise的一些規範需要完善點選此處獲取規範。
符合promisesA+的規範
總結幾點
- 該
try{}catch(){}
的地方都標記上,寧可錯殺不放過。 - onFulfilled和onRejected的方法放入
setTimeout
之中,為了讓他們變成“巨集任務(macro-task)”。(應該是出於效能的考慮,之後再研究。) - 然後加一個
Promise.defer = Promise.deferred = function(){}
方法,防止篡改。 - 接著匯出promise,
module.exports=Promise
。 - 最後執行一波
promises-aplus-tests.cmd 你的promise.js
,然後一行行地檢查你的程式碼,等到全部變綠(passing),恭喜你成功攻克promise!!
//參考p5.js
複製程式碼