前端萌新眼中的Promise及使用

limingru發表於2018-02-26

一個 Promise 就是一個代表了非同步操作最終完成或者失敗的物件。這是MDN上關於Promise的解釋。在前端開發中,Promise經常被拿來用於處理非同步和回撥的問題,來規避回撥地獄和更好排布非同步相關的程式碼。本篇文章對於Promise以及相關的async/await記錄一些自己的理解和體會。

一、Promise的三種狀態

從字面的意思理解,Promise即是承諾,既是承諾,那承諾的結果就會有成功和失敗兩種。而且,我們許下承諾之後不會立即得到結果,在獲得成功或是失敗的結果之前,我們還需要一點時間來履行這個承諾。Promise的構造其實像極了我們生活中的承諾。

Promise

上面這張圖就是Promise的結構圖。就像我們生活中的承諾一樣,Promise也存在三種狀態,一種是履行承諾的pending狀態,一種是承諾失敗時的Rejected狀態,再就是承諾成功時Fullfilled狀態。

接下來,我們以愛情的名義來承諾一下:


let love = new Promise((resolve, reject) => {
    setTimeout(() => {         //開始談戀愛,不過戀愛的結果要以後才知道
        let happy = Math.random() >= 0.3 ? true : false
        if ( happy ) {
            resolve('marry')    //戀愛成功,決定結婚
        } else {
            reject('break')     //戀愛失敗,決定分手
        }    
    }, 500)
})


love.then(result => {
    console.log(result)   //處理戀愛成功的回撥,result是上面resolve傳過來的'marry'
}).catch(result => {
    console.log(result)   //處理戀愛失敗的回撥,result是上面reject傳過來的'break'
})

複製程式碼

上面的程式碼就是一個簡單卻完整的Promise的例子。需要特別注意的是,Promise在經過pending狀態達到成功或失敗狀態時就會凝固,即到達成功狀態後再也不會失敗,失敗以後也不會回到成功狀態。

所以下面的Promise一定是失敗狀態的,即便reject後面跟了resolve也沒用。正所謂:若愛,請深愛,若棄,請徹底,不要曖昧,傷人傷己。柏拉圖這話,說的就是Promise的狀態凝固。

let love = new Promise((resolve, reject) => {
    reject('break')
    resolve('marry')
})

love.then(result => {
    console.log(result)
}).catch(result => {
    console.log(result)
})
複製程式碼

二、Promise的then與catch的幾種寫法

第一種,最常見的就是上面的寫法, 使用then來捕捉resolve狀態,使用catch來捕捉reject狀態

love.then(result => {
    console.log(result)
}).catch(result => {
    console.log(result)
})

複製程式碼

第二種,不寫catch, 把用來捕捉reject狀態的函式也寫到then裡,但是效果和上面一樣

love.then(result => {
    console.log(result)
}, result => {
    console.log(result)
})
複製程式碼

第三種,分開寫,也是可以的

love.then(result => {     //只捕捉和處理成功狀態
    console.log(result)
})

love.catch(result => {    //只捕捉和處理失敗狀態
    console.log(result)  
})
複製程式碼

三、快速構建一個成功或是失敗狀態的Promise

Promise自帶了兩種方法,我們可以利用它們快速構建一個Promise,一個是Promise.resolve(), 用於構建成功狀態的Promise;另一個是Promise.reject(),用於構建失敗狀態的Promise。


let p1 = Promise.resolve('success')
console.log(p1)   // 打出來的是 Promise {'success'}
p1.then(result => {
    console.log(result)  //打出來上面resolve傳過來的字串'success'
})

let p2 = Promise.reject('failed')   //上面是一個成功狀態Promise,這是一失敗狀態的Promise
p2.catch(result => {
    console.log(result)
})

複製程式碼

四、使用Promise.all()來處理一類前端場景

在前端的開發實踐中,我們有時會遇到需要傳送多個請求並根據請求順序返回資料的需求,比如,我們要傳送a、b、c三個請求,這三個請求返回的資料分別為a1、a2、a3,而我們想要a1、a2、a3按照我們希望的順序返回。那麼,使用Promise.all()方法可以完美的解決這一問題。

假設使用程式碼如下:


//模擬非同步請求的函式
let request = (name, time) => {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            let random = Math.random()
            if (random >= 0.2) {
                resolve(`${name}成功了`)
            } else {
                reject(`${name}失敗了`)
            }
        }, time)    
    })
}

//構建三個Promise例項
let a = request('小明', 1000)
let b = request('小紅', 500)
let c = request('小華', 1500)

//使用Promise.all(), 注意它接收的是一個陣列作為引數 
Promise.all([b,a,c]).then(result => {
    console.log(result)
}).catch(result => {
    console.log(result)
})

複製程式碼

把上面的程式碼複製下來放到瀏覽器的除錯控制檯裡多執行幾次(第二次執行需要重新整理)會發生什麼事情呢?你可能猜到了:如果三個請求都成功的話,那麼這三個請求所返回的資料就是按照傳送請求的順序排列的,即['小紅成功了', '小明成功了', '小華成功了'],而且還是以陣列形式返回的;而當其中有請求失敗了的話,就只會返回最先失敗的結果。

當然,除了這個場景以外,Promise.all()方法還能用於其它地方。比如說,一個頁面上有兩個請求,只有拿到了這兩個請求的資料,頁面才會展示,在這之前會顯示一個loading載入圖。使用Promise.all()也是可以非常簡潔的解決這個問題。

五、Promise的鏈式呼叫

上面說過的then方法,在每次使用後依然會繼續返回一個Promise物件。

let p = Promise.resolve('success')
let response = p.then(result => {
    console.log(result)
})
console.log(response)   //打出來的response是一個Promise物件
複製程式碼

因為then之後返回的還是一個Promise物件,那我們就可以繼續then,只不過後面then拿到的引數是上一個then裡return的內容,而這個return的內容既可以是普通的字串、數字等(最後都會被封裝成Promise)也可以是自己寫的一個Promise物件。

接下來我們接著上面愛的承諾繼續寫一個鏈式呼叫的例子:

let love = new Promise((resolve, reject) => {
    setTimeout(() => {
        let happy = Math.random() >= 0.3 ? true : false
        if ( happy ) {
            resolve('marry')
        } else {
            reject('break')
        }    
    }, 500)
})

let haveChild = new Promise((resolve, reject) => {
    setTimeout(() => {
       resolve('孩子生了!') 
    }, 1000)
})


love.then(result => {
    console.log(result)
    return haveChild  // 這裡返回一個Promise物件,它的resolve會被下一個then捕捉
}).then(result => {
    console.log(result)
    return '最後,他們白頭偕老!' //這裡返回的字串會傳給下一個then
}).then(result => {
    console.log(result)
}).catch(error => { 
    console.log(error)
})

複製程式碼

這裡需要注意的是,在鏈式呼叫的最一定要加上一個catch來捕捉鏈條中可能出現的錯誤!

六、Promise鏈式呼叫可以處理的一個業務場景

當我們需要傳送多個請求,而後一個請求總是依賴前一個請求的結果時,Promise的鏈式操作就可以派上用場了。

我們使用axios來演示,axios本身就使用Promise進行封裝,程式碼如下:


let request = (url) => {
    let result = axios.get(url)  //result是Promise物件
    result.then(response => {
        return response
    }).catch(error => {
        throw new Error('出錯了!')
    })
}

request(url0).then(response => {
    return request(response.data.link)
}).then(result => {
    console.log(result)
}).catch(error => {
    console.log(error)
})

複製程式碼

上面的程式碼簡單模擬了一下這個過程,有些地方還不完善。

以上就是我對Promise用法的一點理解,很多地方還不完善,如果出錯,還請各位朋友們能及時指正!

這是我在掘金上的第一篇文章!感謝觀看!

相關文章