您當前的位置:首頁 > 攝影

十行程式碼實現高仿 Promise

作者:由 若愚 發表于 攝影時間:2017-04-21

文章作者:若愚@飢人谷,首發於 前端學習指南 - 知乎專欄。 轉載需在文章顯要位置宣告來源

問題

假設我們有一個需求: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, 更多幹貨等你來拿

標簽: promise  function  resolve  非同步