Promise的探索

關(guān)于本文

Tips: 本文參考標(biāo)準(zhǔn)規(guī)范為 Promises/A+

[鏈接:https://promisesaplus.com/]

Promise 來源

  • 起源:Promise最初被提出是在 E語言中拣凹, 它是基于并列/并行處理設(shè)計(jì)的一種編程語言涧偷。

  • 核心:解決 回調(diào)地獄 的問題

  • 原JS異步處理方式盯滚,存在如下問題:
    非Promise實(shí)現(xiàn)異步處理方式悴了,如下:

        let fs = require('fs');
        fs.readFile('./2.promise/1.txt','utf8',function(err,data){
            fs.readFile(data,'utf8',function(err,data){
                console.log(data);
            });
        });
    

    總結(jié)存在問題(雖然都能解決,但是可拓展性太差):

    • try/catch問題
    • 無法在同一時(shí)刻合并兩次異步的結(jié)果,異步方案不支持return
  • Promise的處理方式

    • 理念:Promise是把類似的異步處理對象和處理規(guī)則進(jìn)行規(guī)范化唐础, 并按照采用統(tǒng)一的接口來編寫勾栗,而采取規(guī)定方法之外的寫法都會出錯(cuò)狰晚。
    var promise = getAsyncPromise("fileA.txt"); 
    
    promise.then(function(result){
        // 獲取文件內(nèi)容成功時(shí)的處理
        ...
    }).catch(function(error){
        // 獲取文件內(nèi)容失敗時(shí)的處理
        ...
    });
    

Promise 狀態(tài)流

用new Promise 實(shí)例化的promise對象有以下三個(gè)狀態(tài)。

  • Pending (es6, has-resolution)狼纬,
    既不是resolve也不是reject的狀態(tài)羹呵。 promise對象剛被創(chuàng)建后的初始化狀態(tài)

  • resolved (es6, has-rejection)
    resolve(成功)時(shí)疗琉,此時(shí)會調(diào)用 onFulfilled

  • rejected (es6冈欢,unresolved)
    reject(失敗)時(shí)盈简。此時(shí)會調(diào)用 onRejected

Promise API

三個(gè)類型凑耻,內(nèi)含部分源碼實(shí)現(xiàn)

  • Constructor
    • 狀態(tài)機(jī) status
    • 處理函數(shù) reject, resolve
    • 執(zhí)行函數(shù) executor
  • Instance Method
    • Resolve callback(onFulfilled會被調(diào)用)

      function resolve(value) { 
          // 成功狀態(tài)
          if (self.status === 'pending') {
              self.status = 'resolved';
              self.value = value;
      
              // 執(zhí)行成功的回調(diào)
              self.onResolvedCallbacks.forEach(function (fn) {
                  fn();
              });
          }
      }
      
    • Reject callback (onRejected 會被調(diào)用)

      function reject(reason) {
          if (self.status === 'pending') {
              self.status = 'rejected';
              self.reason = reason;
      
              // 執(zhí)行是失敗的回調(diào)
              self.onRejectedCallbacks.forEach(function (fn) {
                  fn();
              });
          }
      }
      
      • promise.then 成功和失敗時(shí)都可以使用。 另外在只想對異常進(jìn)行處理時(shí)可以采用 promise.then(undefined, onRejected) 這種方式柠贤,只指定reject時(shí)的回調(diào)函數(shù)即可香浩。
      • 不過這種情況下promise.catch(onRejected) 應(yīng)該是個(gè)更好的選擇。
    • Static Method (靜態(tài)方法)

      • Promise.all

        原理:hold住所有的隊(duì)列中的實(shí)例臼勉,等待所有實(shí)例執(zhí)行完畢后邻吭,執(zhí)行resolve 一起返回

        Promise.all = function(promises) {
            //promises是一個(gè)promise的數(shù)組
            return new Promise(function (resolve, reject) {
                //arr是最終返回值的結(jié)果
                let arr = [];
                // 表示成功了多少次
                let i = 0;
        
                function processData(index, y) {
                    arr[index] = y;
                    if (++i === promises.length) {
                        resolve(arr);
                    }
                }
        
                for (let i = 0; i < promises.length; i++) {
                    promises[i].then(function (y) {
                        processData(i, y)
                    }, reject)
                }
            });
        }
        
      • Promise.catch

        原理:利用的是promise實(shí)例的鏈?zhǔn)秸{(diào)用中,如果return 一個(gè)error flag(例如: null, throw new Error等)宴霸,則直接走到 Reject callback 中

        // 捕獲錯(cuò)誤的方法
        Promise.prototype.catch = function (callback) {
            return this.then(null, callback)
        }
        
      • Promise.race

        釋義:只要有一個(gè)promise成功了 就算成功囱晴。如果第一個(gè)失敗了就失敗了

        Promise.race = function (promises) {
            return new Promise(function (resolve, reject) {
                for (var i = 0; i < promises.length; i++) {
                    promises[i].then(resolve,reject)
                }
            })
        }
        
      • Promise.resolve

        釋義:生成一個(gè)成功的promise

        Promise.resolve = function(value){
            return new Promise(function(resolve,reject){
                resolve(value);
            })
        }
        
      • Promise.reject

        釋義:生成一個(gè)失敗的promise

        Promise.reject = function(reason){
            return new Promise(function(resolve,reject){
                reject(reason);
            })
        }
        

Promise 核心方法實(shí)現(xiàn)

  • 核心特性
    • 多次then的實(shí)現(xiàn)

      • 原理:promise實(shí)例可以多次then膏蚓,當(dāng)成功后會將then中的成功方法按順序執(zhí)行,我們可以先將then中的成功的回調(diào)和失敗的回調(diào)存到數(shù)組內(nèi)畸写,當(dāng)成功時(shí)調(diào)用成功的數(shù)組即可
    • 鏈?zhǔn)秸{(diào)用

      • 原理:promise實(shí)現(xiàn)鏈?zhǔn)秸{(diào)用靠的是返回一個(gè)新的promise
    • 兼容所有返回結(jié)果

      • 有值就進(jìn)入下一個(gè)的then中

        如果then中無論是成功的回調(diào)還是失敗的回調(diào)只要返回了結(jié)果就會走下一個(gè)then中的成功驮瞧,如果有錯(cuò)誤走下一個(gè)then的失敗

      • 普通值,例如 string類型

        如果第一個(gè)promise返回一個(gè)普通值剧董, 會進(jìn)到下一次then的成功的回調(diào)

      • Error 值,例如 throw new Error()

        如果第一個(gè)promise返回一個(gè)Error值破停, 會進(jìn)到下一次then的失敗的回調(diào)

      • promise

        如果第一個(gè)promise返回了一個(gè)promise翅楼,需要等待返回的promise執(zhí)行后的結(jié)果傳遞給下一次then中

    • resolvePromise 處理重復(fù)返回promise的方法

      • 校驗(yàn)循環(huán)引用

        resolvePromise 返回的結(jié)果和promise是同一個(gè)那么永遠(yuǎn)不會成功和失敗,導(dǎo)致循環(huán)引用

        // 預(yù)防循環(huán)引用
        if (promise2 === x) { //這里應(yīng)該報(bào)一個(gè)類型錯(cuò)誤真慢,有問題
            return reject(new TypeError('循環(huán)引用'))
        }
        
    * `校驗(yàn)value是不是一個(gè)promise`
        
        如果x是對象毅臊,并且x的then方法是函數(shù),我們就認(rèn)為他是一個(gè)promise

        ```
        if (x !== null && (typeof x === 'object' || typeof x === 'function')) {
            ....
        }
        ```

    * `校驗(yàn)promise 是否ok`

        有些人寫的promise可能會既調(diào)用成功 又調(diào)用失敗黑界,如果兩個(gè)都調(diào)用先調(diào)用誰另一個(gè)就忽略掉

* promise中`值的穿透`
    * 原理:校驗(yàn)方法是不是又返回值管嬉,如果沒有,則`內(nèi)置回調(diào)方法朗鸠,默認(rèn)把值往下傳遞`
    ```
    //成功和失敗默認(rèn)不穿給一個(gè)函數(shù)
    onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : function (value) {
        return value;
    }
    onRjected = typeof onRjected === 'function' ? onRjected : function (err) {
        throw err;
    }
    ```
* promise規(guī)范中要求蚯撩,`所有的onFufiled和onRjected都需要異步執(zhí)行,setTimeout`
    ```
    setTimeout(function () {
        try {
            let x = onFulfilled(self.value);
            // x可能是別人promise,寫一個(gè)方法統(tǒng)一處理
            resolvePromise(promise2, x, resolve, reject);
        } catch (e) {
            reject(e);
        }
    })
    ```


`如下開始展示源碼實(shí)現(xiàn)`
  • 構(gòu)造函數(shù) Promise 的實(shí)現(xiàn)

    function Promise(executor) { // executor是一個(gè)執(zhí)行函數(shù)
        let self = this;
        self.status = 'pending'; // 狀態(tài)機(jī)烛占,默認(rèn)值為pending胎挎,初始化的值
        self.value = undefined; // 默認(rèn)成功的值
        self.reason = undefined; // 默認(rèn)失敗的原因
        self.onResolvedCallbacks = []; // 存放then成功的回調(diào)
        self.onRejectedCallbacks = []; // 存放then失敗的回調(diào)
    
        // 成功時(shí)的處理函數(shù)
        function resolve(value) { // 成功狀態(tài)
            if (self.status === 'pending') {
                self.status = 'resolved';
                self.value = value;
                self.onResolvedCallbacks.forEach(function (fn) {
                    fn();
                });
            }
        }
    
        // 失敗時(shí)的處理函數(shù)
        function reject(reason) {
            if (self.status === 'pending') {
                self.status = 'rejected';
                self.reason = reason;
                self.onRejectedCallbacks.forEach(function (fn) {
                    fn();
                });
            }
        }
    
        try {
            executor(resolve, reject)
        } catch (e) {
            // 捕獲的時(shí)候發(fā)生異常,就直接失敗了
            reject(e);
        }
    }
    
  • then方法實(shí)現(xiàn)

    1. 步驟1處理了值穿透的問題,case如下:
      promise.then().then().then(function() {
          ...
      });
      

    結(jié)構(gòu)代碼:

    Promise.prototype.then = function (onFulfilled, onRjected) {
        // 步驟1:
        // 成功和失敗默認(rèn)不穿給一個(gè)函數(shù)
        onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : function (value) {
            return value;
        }
        onRjected = typeof onRjected === 'function' ? onRjected : function (err) {
            throw err;
        }
    
        let self = this;
        let promise2;
    
        // 步驟2:
        // 成功的處理
        if (self.status === 'resolved') {
            promise2 = new Promise(function (resolve, reject) {
                // 當(dāng)成功或者失敗執(zhí)行時(shí)有異常那么返回的promise應(yīng)該處于失敗狀態(tài)
                // x可能是一個(gè)promise 也有可能是一個(gè)普通的值
    
                setTimeout(function () {
                    try {
                        let x = onFulfilled(self.value);
                        // x可能是別人promise忆家,寫一個(gè)方法統(tǒng)一處理
                        resolvePromise(promise2, x, resolve, reject);
                    }
                    catch (e) {
                        reject(e);
                    }
                })
            })
        }
    
        // 步驟3:
        // 錯(cuò)誤的處理
        if (self.status === 'rejected') {
            promise2 = new Promise(function (resolve, reject) {
                setTimeout(function () {
                    try {
                        let x = onRjected(self.reason);
                        resolvePromise(promise2, x, resolve, reject);
                    } catch (e) {
                        reject(e);
                    }
                })
            })
        }
    
        // 步驟4:
        // 等待異步執(zhí)行的過程當(dāng)犹菇,即調(diào)用then時(shí)可能沒成功 也沒失敗
        // 此時(shí)還需要同步的回調(diào)函數(shù)push到隊(duì)列中,方便后面的回調(diào)使用
        if (self.status === 'pending') {
            promise2 = new Promise(function (resolve, reject) {
    
                // 此時(shí)沒有resolve 也沒有reject
                self.onResolvedCallbacks.push(function () {
                    setTimeout(function () {
                        try {
                            let x = onFulfilled(self.value);
                            resolvePromise(promise2, x, resolve, reject);
                        } catch (e) {
                            reject(e)
                        }
                    })
                });
    
                self.onRejectedCallbacks.push(function () {
                    setTimeout(function () {
                        try {
                            let x = onRjected(self.reason);
                            resolvePromise(promise2, x, resolve, reject);
                        } catch (e) {
                            reject(e);
                        }
                    })
                });
            })
        }
    
        return promise2;
    }
    
  • 處理函數(shù)芽卿,resolvePromise方法實(shí)現(xiàn)

    function resolvePromise(promise2, x, resolve, reject) {
        // 校驗(yàn):循環(huán)引用
        if (promise2 === x) {
            return reject(new TypeError('循環(huán)引用了'))
        }
    
        // 校驗(yàn):表示是否調(diào)用過成功或者失敗
        let called;
    
        // 校驗(yàn):看x是不是一個(gè)promise揭芍,promise應(yīng)該是一個(gè)對象
        if (x !== null && (typeof x === 'object' || typeof x === 'function')) {
    
            // 可能是promise {},看這個(gè)對象中是否有then方法,如果有then我就認(rèn)為他是promise了
            try {
                let then = x.then;
                if (typeof then === 'function') {
    
                    // 成功
                    then.call(x, function (y) {
                        if (called) return
                        called = true;
    
                        // y可能還是一個(gè)promise卸例,在去解析直到返回的是一個(gè)普通值
                        resolvePromise(promise2, y, resolve, reject)
                    }, function (err) { //失敗
                        if (called) return
                        called = true
    
                        reject(err);
                    })
                } else {
                    resolve(x)
                }
            } catch (e) {
                if (called) return
                called = true;
                reject(e);
            }
        } else {
            // 說明是一個(gè)普通值1
            resolve(x);
        }
    }
    

Promise 語法糖

原理:語法糖負(fù)責(zé)幫你簡化了邏輯称杨,代碼結(jié)構(gòu)如下

Promise.defer = Promise.deferred = function () {
    let dfd = {};
    dfd.promise = new Promise(function (resolve, reject) {
        dfd.resolve = resolve;
        dfd.reject = reject;
    });
    return dfd
}

類似Q.js用法如圖:

let fs = require('fs');
let Q = require('q');

function readFile(url) {
    let defer = Q.defer();

    require('fs').readFile(url, 'utf8', function (err, data) {
        if (err) defer.reject(err);
        defer.resolve(data);
    });

    return defer.promise
}

Promise 的未來發(fā)展

由于es6 generator 以及es7 await/async 的興起,我們將在下一章筷转,繼續(xù)進(jìn)行g(shù)enerator 和 co的結(jié)合的異步處理探索

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末姑原,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子旦装,更是在濱河造成了極大的恐慌页衙,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,470評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異店乐,居然都是意外死亡艰躺,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,393評論 3 392
  • 文/潘曉璐 我一進(jìn)店門眨八,熙熙樓的掌柜王于貴愁眉苦臉地迎上來腺兴,“玉大人,你說我怎么就攤上這事廉侧∫诚欤” “怎么了?”我有些...
    開封第一講書人閱讀 162,577評論 0 353
  • 文/不壞的土叔 我叫張陵段誊,是天一觀的道長闰蚕。 經(jīng)常有香客問我,道長连舍,這世上最難降的妖魔是什么没陡? 我笑而不...
    開封第一講書人閱讀 58,176評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮索赏,結(jié)果婚禮上盼玄,老公的妹妹穿的比我還像新娘。我一直安慰自己潜腻,他們只是感情好埃儿,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,189評論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著融涣,像睡著了一般童番。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上暴心,一...
    開封第一講書人閱讀 51,155評論 1 299
  • 那天妓盲,我揣著相機(jī)與錄音杂拨,去河邊找鬼专普。 笑死,一個(gè)胖子當(dāng)著我的面吹牛弹沽,可吹牛的內(nèi)容都是我干的檀夹。 我是一名探鬼主播,決...
    沈念sama閱讀 40,041評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼策橘,長吁一口氣:“原來是場噩夢啊……” “哼炸渡!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起丽已,我...
    開封第一講書人閱讀 38,903評論 0 274
  • 序言:老撾萬榮一對情侶失蹤蚌堵,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體吼畏,經(jīng)...
    沈念sama閱讀 45,319評論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡督赤,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,539評論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了泻蚊。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片躲舌。...
    茶點(diǎn)故事閱讀 39,703評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖性雄,靈堂內(nèi)的尸體忽然破棺而出没卸,到底是詐尸還是另有隱情,我是刑警寧澤秒旋,帶...
    沈念sama閱讀 35,417評論 5 343
  • 正文 年R本政府宣布约计,位于F島的核電站,受9級特大地震影響迁筛,放射性物質(zhì)發(fā)生泄漏病蛉。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,013評論 3 325
  • 文/蒙蒙 一瑰煎、第九天 我趴在偏房一處隱蔽的房頂上張望铺然。 院中可真熱鬧,春花似錦酒甸、人聲如沸魄健。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,664評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽沽瘦。三九已至,卻和暖如春农尖,著一層夾襖步出監(jiān)牢的瞬間析恋,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,818評論 1 269
  • 我被黑心中介騙來泰國打工盛卡, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留助隧,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,711評論 2 368
  • 正文 我出身青樓滑沧,卻偏偏與公主長得像并村,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子滓技,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,601評論 2 353

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

  • 本文適用的讀者 本文寫給有一定Promise使用經(jīng)驗(yàn)的人哩牍,如果你還沒有使用過Promise,這篇文章可能不適合你令漂,...
    HZ充電大喵閱讀 7,305評論 6 19
  • 前言 本文旨在簡單講解一下javascript中的Promise對象的概念膝昆,特性與簡單的使用方法丸边。并在文末會附上一...
    _暮雨清秋_閱讀 2,197評論 0 3
  • 1.promise簡介 1.1 Promise本意是承諾,在程序中的意思就是承諾我過一段時(shí)間后會給你一個(gè)結(jié)果...
    常青_1f93閱讀 836評論 0 1
  • Promise 對象 Promise 的含義 Promise 是異步編程的一種解決方案荚孵,比傳統(tǒng)的解決方案——回調(diào)函...
    neromous閱讀 8,705評論 1 56
  • 今天我們組織選拔上方陣的隊(duì)員原环,可是還沒開始我已經(jīng)知道我被選上的概率不高,我知道我自己的動(dòng)作不夠別人好处窥,可是就是不爽...
    培根在找蛋閱讀 300評論 0 1