百因必有果,你的「Promise」就是我

前言

不得不說, promise 這玩意,是每一個面試官都會問的問題,可是你真的瞭解promise嗎?其實我也不瞭解,下面的內容都是我從掘金、知乎、《ECMAScript6入門》上看的博客文章等資料,而後總結的,畢竟本身寫一遍,更有助於理解,若有錯誤,請指出 ~javascript

什麼是回調地獄 ?

在過去寫異步代碼都要靠回調函數,當異步操做依賴於其餘異步操做的返回值時,會出現一種現象,被程序員稱爲 「回調地獄」,好比這樣 :前端

// 假設咱們要請求用戶數據信息,它接收兩個回調,假設咱們要請求用戶數據信息,它接收兩個回調,successCallback 和 errCallback

    function getUserInfo (successCallback, errCallback) {
        $.ajax({
            url : 'xxx',
            method : 'get',
            data : {
                user_id : '123'
            },
            success : function(res) {
                successCallback(res)    // 請求成功,執行successCallback()回調
            },
            error : function(err) {
                errCallback(err)        // 請求失敗,執行errCallback()回調
            }
        })
    }
複製代碼

騙我 ? 這哪裏複雜了,明明很簡單啊,說好的回調地獄呢 ? 不急,繼續看java

假設咱們拿到了用戶信息,可是咱們還要拿到該用戶的聊天列表,而後再拿到跟某一「陌生」男人的聊天記錄呢 ?程序員

// getUserInfo -> getConnectList -> getOneManConnect()

    getUserInfo((res)=>{
        getConnectList(res.user_id, (list)=>{
            getOneManConnect(list.one_man_id, (message)=>{
                console.log('這是我和某位老男人的聊天記錄')
            }, (msg_err)=>{
                console.log('獲取詳情失敗,別污衊我,我不跟老男人聊天')
            })
        }, (list_err)=>{
            console.log('獲取列表失敗,我都不跟別人聊天')
        })
    }, (user_err)=>{
        console.log('獲取用戶我的信息失敗')
    })
複製代碼

大兄弟,刺激不,三層嵌套,再多來幾個嵌套,就是 「回調地獄」 了。這時候,promise來了。es6

Promise 簡介

阮一峯老師的《ECMAScript 6入門》裏對promise的含義是 : Promise 是異步編程的一種解決方案,簡單說就是一個容器,裏面保存着某個將來纔會結束的事件(一般是一個異步操做)的結果。從語法上說,Promise 是一個對象,從它能夠獲取異步操做的消息。Promise 提供統一的 API,各類異步操做均可以用一樣的方法進行處理。面試

簡單來講,Promise就是對異步的執行結果的描述對象。ajax

狀態

  • pending (進行中)
  • fulfilled (已成功)
  • rejected (已失敗)
1 : 只有異步操做的結果,能夠決定當前是哪種狀態,任何其餘操做都沒法改變這個狀態。
    2 : 一旦狀態改變,就不會再變,任什麼時候候均可以獲得這個結果。
    3 : Promise對象的狀態改變,只有兩種可能:從pending變爲fulfilled和從pending變爲rejected
複製代碼

知乎形象例子來講明promise

// 定外賣就是一個Promise,Promise的意思就是承諾
// 咱們定完外賣,飯不會當即到咱們手中
// 這時候咱們和商家就要達成一個承諾
// 在將來,無論飯是作好了仍是燒糊了,都會給咱們一個答覆
function 定外賣(){
    // Promise 接受兩個參數
    // resolve: 異步事件成功時調用(菜燒好了)
    // reject: 異步事件失敗時調用(菜燒糊了)
    return new Promise((resolve, reject) => {
        let result = 作飯()
	// 下面商家給出承諾,無論燒沒燒好,都會告訴你
	if (result == '菜燒好了') 
	    // 商家給出了反饋
	    resolve('咱們的外賣正在給您派送了')
	else 
	    reject('很差意思,咱們菜燒糊了,您再等一會')
	})
}

// 商家廚房作飯,模擬機率事件
function 作飯() {
    return Math.random() > 0.5 ? '菜燒好了' : '菜燒糊了'
}

// 你在家上餓了麼定外賣
// 有一半的機率會把你的飯燒糊了
// 好在有承諾,他仍是會告訴你

定外賣()
    // 菜燒好執行,返回'咱們的外賣正在給您派送了'
    .then(res => console.log(res))
    // 菜燒糊了執行,返回'很差意思,咱們菜燒糊了,您再等一會'
    .catch(res => console.log(res))

複製代碼

基本用法

Promise 對象是一個構造函數,用來生成一個Promise實例。npm

Promise構造函數接受一個函數做爲參數,這個函數有兩個參數,分別是resolve()和reject()。

    resovle()函數是將Promise對象從pending變成fulfilled,在異步操做完成時執行,將異步結果,做爲參數傳遞出去。

    reject()函數是將Promise對象從pending變成rejected,在異步執行失敗時執行,將報錯信息,做爲參數傳遞出去。

    // 簡單的一個promise實例, 來自阮一峯老師的es6 示例代碼
    const promise = new Promise((resolve, reject) => {
        // some code 

        if(/* 異步執行成功 */) {
            resolve(res)
        } else {
            reject(error)
        }
    })
複製代碼

then方法

Promise 有個.then()方法,then 方法中的回調在微任務隊列中執行,支持傳入兩個參數,一個是成功的回調,一個是失敗的回調,在 Promise 中調用了 resolve 方法,就會在 then 中執行成功的回調,調用了 reject 方法,就會在 then 中執行失敗的回調,成功的回調和失敗的回調只能執行一個,resolve 和 reject 方法調用時傳入的參數會傳遞給 then 方法中對應的回調函數。編程

// 執行 resolve 
    let promise = new Promise((resolve, reject) => {
        console.log(1)
        resolve(3)
    })

    console.log(2)

    promise.then((data)=>{
        console.log(data)
    }, (err)=>{
        console.log(err)
    })

    // 1
    // 2
    // 3
複製代碼
// 執行 reject 
    let promise = new Promise((resolve, reject) => {
        console.log(1)
        reject()
    })

    promise.then(()=>{
        console.log(2)
    }, ()=>{
        console.log(3)
    })

    // 1
    // 3
複製代碼

then方法

[ 注意 : then方法中的回調是異步的!!!]segmentfault

爲何上面第一個示例代碼的結果是 1 -> 2 -> 3呢 ?傳入Promise 中的執行函數是當即執行完的啊,爲何不是當即執行 then 中的回調呢?由於then 中的回調是異步執行,表示該回調是插入事件隊列末尾,在當前的同步任務結束以後,下次事件循環開始時執行隊列中的任務。

Promise 的回調函數不是正常的異步任務,而是微任務(microtask)。它們的區別在於,正常任務追加到下一輪事件循環,微任務追加到本輪事件循環。這意味着,微任務的執行時間必定早於正常任務

then方法的返回值是一個新的GPromise對象,這就是爲何promise可以進行鏈式操做的緣由。

then方法中的一個難點就是處理異步,經過setInterval來監聽GPromise對象的狀態改變,一旦改變,就是執行GPromise對應的then方法中相應的回調函數。這樣回調函數就可以插入事件隊列末尾,異步執行。
複製代碼
then有兩個參數 : onFulfilled 和 onRejected
    
    · 當狀態state爲fulfilled,則執行onFulfilled,傳入this.value。當狀態state爲rejected,則執行onRejected,傳入this.reason

    · onFulfilled,onRejected若是他們是函數,則必須分別在fulfilled,rejected後被調用,value或reason依次做爲他們的第一個參數

    class Promise{
        constructor(executor){...}
        // then 方法 有兩個參數onFulfilled onRejected
        then(onFulfilled,onRejected) {
            // 狀態爲fulfilled,執行onFulfilled,傳入成功的值
            if (this.state === 'fulfilled') {
                onFulfilled(this.value);
            };
            // 狀態爲rejected,執行onRejected,傳入失敗的緣由
            if (this.state === 'rejected') {
                onRejected(this.reason);
            };
        }
    }

複製代碼

Promise的鏈式調用

因爲promise每次調用then方法就會返回一個新的promise對象,若是該then方法中執行的回調函數有返回值,那麼這個返回值就會做爲下一個promise實例的then方法回調的參數,若是 then 方法的返回值是一個 Promise 實例,那就返回一個新的 Promise 實例,將 then 返回的 Promise 實例執行後的結果做爲返回 Promise 實例回調的參數。

還記得剛開頭說的那個「陌生」男人例子嗎 ?這裏咱們用promise的鏈式操做重寫下

// 原來的代碼
    getUserInfo((res)=>{
        getConnectList(res.user_id, (list)=>{
            getOneManConnect(list.one_man_id, (message)=>{
                console.log('這是我和某位老男人的聊天記錄')
            }, (msg_err)=>{
                console.log('獲取詳情失敗,別污衊我,我不跟老男人聊天')
            })
        }, (list_err)=>{
            console.log('獲取列表失敗,我都不跟別人聊天')
        })
    }, (user_err)=>{
        console.log('獲取用戶我的信息失敗')
    })

    
    // Promise重寫的代碼
    function handleAjax (params) {
        return new Promise((resolve, reject)=>{
            $.ajax({
                url : params.url,
                type : params.type || 'get',
                data : params.data || '',
                success : function(data) {
                    resolve(data)
                },
                error : function(error) {
                    reject(error)
                }
            })
        })
    }

    const promise = handleAjax({
        url : 'xxxx/user'
    });

    promise.then((data1)=>{
        console.log('獲取我的信息成功')       // 獲取我的信息成功
        return handleAjax({
            url : 'xxxx/user/connectlist',
            data : data1.user_id
        });
    })
    .then((data2)=>{
        console.log('得到聊天列表')
        return handleAjax({
            url : 'xxxx/user/connectlist/one_man',
            data : data2.one_man_id
        });
    })
    .then((data3)=>{
        console.log('得到跟某男人的聊天')
    })
    .catch((err)=>{
        console.log(err)
    }) 
複製代碼

來自ES6的 Promise.prototype.then()

Promise 實例具備then方法,也就是說,then方法是定義在原型對象Promise.prototype上的。它的做用是爲 Promise 實例添加狀態改變時的回調函數。前面說過,then方法的第一個參數是resolved狀態的回調函數,第二個參數(可選)是rejected狀態的回調函數。

then方法返回的是一個新的Promise實例(注意,不是原來那個Promise實例)。所以能夠採用鏈式寫法,即then方法後面再調用另外一個then方法。

採用鏈式的then,能夠指定一組按照次序調用的回調函數。這時,前一個回調函數,有可能返回的仍是一個Promise對象(即有異步操做),這時後一個回調函數,就會等待該Promise對象的狀態發生變化,纔會被調用

來自ES6的 Promise.prototype.catch()

Promise.prototype.catch方法是.then(null, rejection)的別名,用於指定發生錯誤時的回調函數。Promise對象狀態變爲resolved,則會調用then方法指定的回調函數;若是異步操做拋出錯誤,狀態就會變爲rejected,就會調用catch方法指定的回調函數,處理這個錯誤。另外,then方法指定的回調函數,若是運行中拋出錯誤,也會被catch方法捕獲。

Promise 對象的錯誤具備「冒泡」性質,會一直向後傳遞,直到被捕獲爲止。也就是說,錯誤老是會被下一個catch語句捕獲

通常來講,不要在then方法裏面定義 reject 狀態的回調函數(即then的第二個參數),老是使用catch方法。

來自ES6的 Promise.all()

Promise.all方法用於將多個 Promise 實例,包裝成一個新的 Promise 實例。

const p = Promise.all([p1, p2, p3])
複製代碼

Promise.all方法接受一個數組做爲參數,p一、p二、p3都是 Promise 實例,若是不是,就會先調用下面講到的Promise.resolve方法,將參數轉爲 Promise 實例,再進一步處理。

p的狀態由p一、p二、p3決定,分紅兩種狀況。

(1)只有p一、p二、p3的狀態都變成fulfilled,p的狀態纔會變成fulfilled,此時p一、p二、p3的返回值組成一個數組,傳遞給p的回調函數。

(2)只要p一、p二、p3之中有一個被rejected,p的狀態就變成rejected,此時第一個被reject的實例的返回值,會傳遞給p的回調函數。
複製代碼

來自ES6 的Promise.race()

Promise.race方法一樣是將多個 Promise 實例,包裝成一個新的 Promise 實例。

const p = Promise.all([p1, p2, p3])
複製代碼

上面代碼中,只要p一、p二、p3之中有一個實例率先改變狀態,p的狀態就跟着改變。那個率先改變的 Promise 實例的返回值,就傳遞給p的回調函數

Promise.race方法的參數與Promise.all方法同樣,若是不是 Promise 實例,就會先調用下面講到的Promise.resolve方法,將參數轉爲 Promise 實例,再進一步處理。

來自ES6 的Promise.resolve()

有時須要將現有對象轉爲 Promise 對象,Promise.resolve方法就起到這個做用

Promise.resolve('test')
    // 等價於
    new Promise(resolve => resolve('test'))

    // 更多請看阮一峯老師的ES6 Promise對象
複製代碼

來自ES6 的Promise.reject()

Promise.reject(reason)方法也會返回一個新的 Promise 實例,該實例的狀態爲rejected。

const p = Promise.reject('出錯了');
    // 等同於
    const p = new Promise((resolve, reject) => reject('出錯了'))

    p.then(null, function (err) {
        console.log(err)    // 出錯了
    });
    
    // 更多請看阮一峯老師的ES6 Promise對象
複製代碼

其餘文章

相關連接

阮一峯 ES6 : es6.ruanyifeng.com/#docs/promi…

知乎例子 : zhuanlan.zhihu.com/p/29632791

掘金 卡姆愛卡姆 : juejin.im/post/5b2f02…

來自segmentfault 的GEEK做者 : segmentfault.com/a/119000001…

我的博客 : blog.pengdaokuan.cn:4001

相關文章
相關標籤/搜索