快速入門上手JavaScript中的Promise

當我還是一個小白的時候,我翻了很多關(guān)于Promise介紹的文檔,我一直沒能理解所謂解決異步操作的痛點是什么意思
直到我翻了谷歌第一頁的所有中文文檔我才有所頓悟,其實從他的英文字面意思理解最為簡單粗暴
這就是一個承諾,相當于在代碼中提供一個在任何時候承諾之后要做什么事的方式,這個承諾可能會兌現(xiàn)也可能無法兌現(xiàn),當然也可能在兌現(xiàn)的過程中
用這個來替代我們曾經(jīng)需要寫的回調(diào)函數(shù),可以避免JavaScript程序中的回調(diào)地獄
所以先不去學習的語法,從另一個方式先理解,希望可以幫助你更好的學習或上手Promise

什么是Promise?

Promise是異步編程的一種解決方案,可以替代傳統(tǒng)的解決方案,比如回調(diào)函數(shù)和事件
其在ES6中統(tǒng)一了用法,并提供了原生的Promise對象Promise對象表示異步操作的最終完成(或失敗)及其結(jié)果值
作為對象,Promise有以下兩個特點:

  1. 對象的狀態(tài)不受外界影響
    • 只有異步操作的結(jié)果,可以決定當前是哪一種狀態(tài),任何其他操作都無法改變這個狀態(tài),
      這也是Promise這個名字的由來,它的英語意思就是承諾,表示其他手段無法改變
  2. 一旦狀態(tài)改變了就不會在變,也就是說任何時候Promise都只有一種狀態(tài)
    • pending:初始狀態(tài),不是成功或失敗狀態(tài);
      fulfilled(resolved):操作成功完成狀態(tài);
      rejected:操作失敗狀態(tài)
    • Promise對象的狀態(tài)改變,只有兩種可能:從Pending變?yōu)?code>Resolved和從Pending變?yōu)?code>Rejected,
      只要這兩種情況發(fā)生,狀態(tài)就凝固了,不會再變了,會一直保持這個結(jié)果,
      就算改變已經(jīng)發(fā)生了,你再對Promise對象添加回調(diào)函數(shù),也會立即得到這個結(jié)果,
      這與事件(Event)完全不同,事件的特點是,如果你錯過了它,再去監(jiān)聽,是得不到結(jié)果的

Promise的創(chuàng)建和用途

  1. 構(gòu)造函數(shù)接受一個名為executor的函數(shù),此執(zhí)行函數(shù)接受兩個f函數(shù)參數(shù),resolvereject
new Promise( /* executor */ function(resolve, reject { ... }) );
  1. Promise通常用于阻塞代碼和異步操作,其中包括文件調(diào)用,API調(diào)用,DB調(diào)用,IO調(diào)用等等
  2. 這些異步操作的啟動發(fā)生在執(zhí)行函數(shù)中,如果異步操作成功,則通過promise的創(chuàng)建者調(diào)用resolve()函數(shù)返回預(yù)期結(jié)果,
    同樣,如果出現(xiàn)意外錯誤,則通過調(diào)用reject()函數(shù)傳遞錯誤具體信息
  3. 由于promise會立即執(zhí)行,我們無法檢查promise的初始狀態(tài),所以創(chuàng)建一個需要時間執(zhí)行的promise最簡單的方式就是
    使用setTimeOut()函數(shù),通過瀏覽器控制臺的輸出可以直觀看到之前所說的狀態(tài),以及他的變化過程
    var promiseFir = new Promise(function(resolve, reject) {
        setTimeout(function() {
            resolve({
                message: '與大家分享知識很開心!',
                code: 200
            });
        }, 2000)
    })
    console.log(promiseFir);
    setTimeout(function() {
        console.log(promiseFir);
    }, 2000)

Promise的方法

  1. 3種原型方法
    // 用一個小故事舉例,你是一個上學的孩子,你問你的媽媽要一個電話。她說: 這個月底我要買一部手機
    var momsPromise = new Promise(function(resolve, reject) {
        momsSavings = 20000;  // 因為媽媽沒有足夠儲蓄所以無法買到禮物  
        // momsSavings = 200000;  // 如果媽媽有足夠的儲蓄就可以買到禮物
        priceOfPhone = 60000;
        if (momsSavings > priceOfPhone) {
            resolve({
                brand: "iphone",
                model: "6s"
            });
        } else {
            reject("我們沒有足夠的儲蓄,讓我們多存點錢吧。");
        }
    });
    momsPromise.then(function(value) {
        console.log("哇,我得到這個電話作為禮物 ", JSON.stringify(value));
    });
    momsPromise.catch(function(reason) {
        console.log("媽媽不能給我買電話,因為 ", reason);
    });
    momsPromise.finally(function() {
        console.log(
            "不管媽媽能不能給我買個電話,我仍然愛她"
        );
    });
  1. 4種靜態(tài)方法

<span id="jumpId-then"></span>

Promise.prototype.then(onFulfilled, onRejected) 鏈式操作

  1. Promise.prototype.then()方法返回的是一個新的Promise對象,因此可以采用鏈式寫法
  2. Promise.prototype.then()方法帶有以下三個參數(shù):成功回調(diào),失敗回調(diào),前進回調(diào),一般情況下只需要實現(xiàn)第一個,后面是可選的
    function ptFir() {
        return new Promise(function(resolve, reject) {
            setTimeout(function() {
                console.log('方法1執(zhí)行');
                resolve('方法1執(zhí)行完畢');
            }, 2000)
        })
    }
    function ptSec() {
        return new Promise(function(resolve, reject) {
            setTimeout(function() {
                console.log('方法2執(zhí)行');
                resolve('方法2執(zhí)行完畢');
            }, 1000)
        })
    }
    function ptThi() {
        return new Promise(function(resolve, reject) {
            setTimeout(function() {
                console.log('方法3執(zhí)行');
                resolve('方法3執(zhí)行完畢');
            }, 3000)
        })
    }
    ptFir().then(function(data) {
        console.log('第一個回調(diào):' + data);
        return ptSec();
    }).then(function(data) {
        console.log('第二個回調(diào):' + data);
        return ptThi();
    }).then(function(data) {
        console.log('第三個回調(diào):' + data);
        return '還沒完?該結(jié)束了吧!';
    }).then(function(data) {
        console.log(data);
    })

<span id="jumpId-catch"></span>

Promise.prototype.catch(onRejected) 捕捉錯誤

  1. Promise.prototype.catch()方法是Promise.prototype.then(null, rejection)的別名,用于指定發(fā)生錯誤時的回調(diào)函數(shù)
  2. Promise對象的錯誤具有"冒泡"性質(zhì),會一直向后傳遞,直到被捕獲為止,也就是說,錯誤總是會被下一個catch語句捕獲
    promise().then(function(data) {
        // todo something
    }).catch(function(err) {
        // 處理上個回調(diào)函數(shù)的錯誤  
    })

<span id="jumpId-finally"></span>

Promise.prototype.finally(onFinally) 最終操作

不管promise對象最后的狀態(tài),在執(zhí)行完.thencatch指定的回調(diào)函數(shù)以后,都會執(zhí)行finally方法指定的回調(diào)函數(shù)

    promise().then(function(data) {
        // todo something
    }).catch(function(err) {
        // 處理上個回調(diào)函數(shù)的錯誤  
    }).finally(function(result) {
        // 處理then/catch之后必須執(zhí)行的操作
    })

<span id="jumpId-resolve-reject"></span>

Promise.resolve() / Promise.reject()

  1. 這兩個是幫助方法或快捷方式,它們可以幫助您輕松創(chuàng)建resolvereject方法
  2. 需要注意的是:如果Promise.resolve()方法的參數(shù),
    不是具有.then()方法的對象(又稱thenable對象),則返回一個新的Promise對象,且它的狀態(tài)為fulfilled
    如果Promise.resolve方法的參數(shù)是一個Promise對象的實例,則會被原封不動地返回
  3. Promise.reject()方法同上
    var promise = Promise.resolve('Hello,這里是執(zhí)行成功的內(nèi)容!');
    // var promise = Promise.reject('出錯了,這里是被發(fā)現(xiàn)的錯誤!');  // 成功和失敗只會有一種狀態(tài)哦  
    promise.then(function(data) {
        console.log(data)
    });
    promise.catch(function(err) {
        console.log(err)
    });

<span id="jumpId-all"></span>

Promise.all()

  1. 當你處理多個promise時,最好先創(chuàng)建一個promise數(shù)組,然后對這些promise集執(zhí)行必要的操作
  2. Promise.all(iterable) 方法返回一個Promise實例,此實例在iterable參數(shù)內(nèi)所有的promise
  3. 都完成(resolved)或參數(shù)中不包含promise時回調(diào)完成(resolve)
  4. 如果參數(shù)中promise有一個失敗(rejected),此實例回調(diào)失敗(reject),失敗原因的是第一個失敗promise的結(jié)果
  5. 注意!!!這里的異步操作是并行執(zhí)行的,等到它們都執(zhí)行完后才會進到then()里面,
    并且all()方法會把所有異步操作的結(jié)果放進一個數(shù)組中傳給then()
  6. 下面的示例需要說明的內(nèi)容
    • 只有3個方法的狀態(tài)都變成fulfilled,p的狀態(tài)才會變成fulfilled,此時3個方法的返回值組成一個數(shù)組,傳遞給p的回調(diào)函數(shù)
    • 只要3個方法之中有一個被rejected,p的狀態(tài)就變成rejected,此時第一個被reject的實例的返回值,會傳遞給p的回調(diào)函數(shù)
    function ptFir() {
        return new Promise(function(resolve, reject) {
            setTimeout(function() {
                console.log('方法1執(zhí)行');
                resolve('方法1執(zhí)行完畢');
            }, 2000)
        })
    }
    function ptSec() {
        return new Promise(function(resolve, reject) {
            setTimeout(function() {
                console.log('方法2執(zhí)行');
                resolve('方法2執(zhí)行完畢');
            }, 1000)
        })
    }
    function ptThi() {
        return new Promise(function(resolve, reject) {
            setTimeout(function() {
                console.log('方法3執(zhí)行');
                resolve('方法3執(zhí)行完畢');
            }, 3000)
        })
    }
    Promise.all([ptFir(), ptSec(), ptThi()]).then(function(data) {
        console.log(data);
        console.log({}.toString.call(data));
    })

<span id="jumpId-race"></span>

Promise.race()

  1. Promise.race(iterable)方法返回一個promise,一旦迭代器中的某個promise解決或拒絕,返回的promise就會解決或拒絕
  2. all()方法的效果實際上是「誰跑的慢,以誰為準執(zhí)行回調(diào)」
    那么相對的就有另一個方法「誰跑的快,以誰為準執(zhí)行回調(diào)」,這就是race()方法,這個詞本來就是賽跑的意思
  3. 這個race()方法有什么用呢?使用場景還是很多的,比如我們可以用race()給某個異步請求設(shè)置超時時間,
    并且在超時后執(zhí)行相應(yīng)的操作
    // ptFir(), ptSec(), ptThi() 同上
    Promise.all([ptFir(), ptSec(), ptThi()]).then(function(data) {
        console.log(data);
        console.log({}.toString.call(data));
    })

使用Promise的一些經(jīng)驗法則

  1. 進行異步操作或使用阻塞代碼時,請使用Promise
  2. 為了代碼的可讀性,resolve()方法對待.then(), reject()方法對應(yīng).catch()
  3. 確保同時寫入.catch().then()方法來實現(xiàn)所有的promise
  4. 如果在這兩種情況下都需要做一些事情,請使用.finally()
  5. 我們只有一次改變每個promise (單一原則)
  6. 我們可以在一個promise中添加多個處理程序
  7. Promise對象中所有方法的返回類型,無論是靜態(tài)方法還是原型方法,都是Promise
  8. all()方法中,無論哪個promise首先未完成,promise的順序都保持在值變量中

參考文檔一 ———— 徹底理解Javascript 中的 Promise
參考文檔二 ———— 通俗淺顯的理解Promise中的then
參考文檔三 ———— 理解 Javascript 中的 Promise
參考文檔四 ———— JavaScript Promise 對象

我是 fx67ll.com,如果您發(fā)現(xiàn)本文有什么錯誤,歡迎在評論區(qū)討論指正,感謝您的閱讀!
如果您喜歡這篇文章,歡迎訪問我的 本文github倉庫地址,為我點一顆Star,Thanks~ :)
轉(zhuǎn)發(fā)請注明參考文章地址,非常感謝!!!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

推薦閱讀更多精彩內(nèi)容