十行程式碼實現高仿 Promise
文章作者:若愚@飢人谷,首發於 前端學習指南 - 知乎專欄。 轉載需在文章顯要位置宣告來源
問題
假設我們有一個需求:1。 獲取使用者所在的城市;2。 根據城市獲取天氣;3。 根據天氣獲取出行建議。那我們的程式碼應該是這樣的
getCity(url1, function(){
getWeather(url2, function(weather){
getSuggestion(url3, function(suggestion){
console。log(suggestion)
})
})
})
這就是典型的非同步 callback 『回撥地獄』,程式碼層層巢狀可讀性很差。關於非同步的解決方式可參考這篇文章 Node。js非同步漫談 - 知乎專欄
使用 Promise 是解決上述問題的一種方式,這裡我們不去講如何去使用內建的 Promise,而是帶大家手把手寫一個 Promise。
思路
我們希望有一個工具,能讓我們使用下面的的寫法來實現上述功能
promise。then(getCity)
。then(getWeather)
。then(getSuggestion)
整理下思路:
Tool 是一個物件
Tool 有 then 這個方法
執行 then 方法返回的應該還是 Tool 物件
function Promise(){}
Promise。prototype。then = function(fn){
//todo。。。
return this
}
var promise = new Promise()
那如何實現非同步操作序列執行呢?關鍵思路如下:
在 promise 物件內容維護一個數組,當執行
promise.then(getCity) .then(getWeather) .then(getSuggestion)
時把這幾個函式依次放入陣列中。注意此時這些函式並沒有執行。
執行promise.resolve()時,會從陣列中拿出一個函式去執行。函式執行的過程中在非同步操作的結果到來後會再次自動呼叫 promise.resolve(),觸發下一個函式的取出並執行,下一個函式結果到來後再次自動呼叫
promise.resolve()
……,這樣就實現了非同步鏈式執行。和原子彈爆炸原理類似。
所以需要對原來的非同步函式做一點小小的改動,在資料到來的地方,加一個promise。resolve,用於啟動後續函式的執行
function
getCity
(){
var
xhr
=
new
XMLHttpRequest
()
xhr
。
open
(
url
,
‘get’
,
true
)
xhr
。
onload
=
function
(){
if
(
this
。
status
==
200
)
{
promise
。
resolve
(
xhr
。
responseText
)
//注意這裡的promise。resolve
}
}
xhr
。
send
()
}
現在我們就能實現一個簡易的 Promise 了,這裡我們先暫不考慮特殊情況:
function Promise(){
this。callbacks = []
}
Promise。prototype。then = function(fn){
this。callbacks。push(fn) //呼叫 then 時把函式放入陣列
return this //返回當前物件供鏈式呼叫
}
Promise。prototype。resolve = function(data){
var fn = this。callbacks。shift() //當呼叫resolve時拿出一個函式
fn&&fn(data) //執行這個函式,並且把resolve的引數做引數
}
var promise = new Promise()
promise。then(getCity)
。then(getWeather)
。then(getSuggestion)
promise。resolve() //啟動
function getCity(){
setTimeout(function(){
promise。resolve(‘杭州’)
}, 1000)
}
function getWeather(city){
setTimeout(function(){
promise。resolve(city + ‘ 晴天’)
}, 1000)
}
function getSuggestion(weather){
setTimeout(function(){
console。log(weather + ‘ 天氣不錯,可攜女友與狗出行’)
}, 1000)
}
當然,如果覺得promise。resolve 單獨啟動一次看起來不舒服,也可以這樣執行
getCity
()
。
then
(
getWeather
)
。
then
(
getSuggestion
)
function
getCity
(){
setTimeout
(
function
(){
promise
。
resolve
(
‘杭州’
)
},
1000
)
return
promise
//注意這裡
}
實現
到此為止我們已經寫了一個簡單的 Promise,甚至能滿足很大一部分使用需求。但有個問題,每次非同步操作可能存在失敗的情況,而上面的程式碼並沒有非同步函式的失敗處理。下面考慮非同步的失敗處理,原理和上面類似,可以閱讀程式碼動手做個測試
class Promise {
constructor (){
this。callbacks = []
this。oncatch = null
}
reject(result){
this。complete(‘reject’, result)
}
resolve(result){
this。complete(‘resolve’, result)
}
complete(type, result){
if(type===‘reject’ && this。oncatch){
this。callbacks = []
this。oncatch(result)
}else if(this。callbacks[0]) {
var handlerObj = this。callbacks。shift()
if(handlerObj[type]){
handlerObj[type](result)
}
}
}
then(onsuccess, onfail){
this。callbacks。push({
resolve: onsuccess,
reject: onfail
})
return this
}
catch(onfail){
this。oncatch = onfail
return this
}
}
var promise = new Promise()
fn1()。then(fn2, onfn1error)
。then(fn3, onfn2error)
。catch(onerror)
function fn1(){
setTimeout(function(){
if(Math。random()>0。5){
promise。resolve(‘杭州’)
}else{
promise。reject(‘fn1 error’)
}
})
}
總結
現在我們已經手寫了一個 Promise,理解 Promise 的原理並熟練使用應該不再話下了吧,加油~
加入大前端QQ學習群9群:542597149, 更多幹貨等你來拿