【ES6】Promise對象的詳解與實現

Promise對象的詳解與實現

參考資料:《ES6標準入門第3版》

目錄

Promise對象的詳解與實現

Promise 對象詳解

1. 什麼是Promise?

Promise 對象的三種狀態

2.2 基本用法

2.3 Promise 的 then() 方法

2.4 Promise 的 catch() 方法

2.5 Promise.resolve() 和 Promise.reject() 把現有對象轉爲Promise對象

2.5.1 參數是一個Promise實例

2.5.2 參數是一個帶有then方法的對象

2.5.3 參數不是具有then方法的對象或根本不是對象

2.5.4 不帶任何參數

2.5.5 Promise.reject() 

2.6 Promise.all() 和 Promise.race()

Promise對象的實現


一 、Promise對象詳解

1. 什麼是Promise?

Promise 是異步編程的一種解決方案,比傳統的解決方案——回調函數和事件更合理和更強大,解決了傳統解決方案處理異步編程出現回調地獄的情況。它由社區最早提出和實現,ES6 將其寫進了語言標準,統一了用法,原生提供了 Promise 對象。

所謂 Promise,簡單說就是一個容器,裏面保存着某個未來纔會結束的事件(通常是一個異步操作)的結果。從語法上說,Promise 是一個對象,從它可以獲取異步操作的消息。Promise 提供統一的 API,各種異步操作都可以用同樣的方法進行處理。

1.1 Promise對象的三種狀態

Promise對象代表着一個異步操作,它有三種狀態:pending(異步操作進行中)、fulfilled(異步操作已完成)、rejected(異步操作已經失敗)

1.2 Promise對象的三個特點

(1)對象的狀態不受外界影響。Promise對象代表一個異步操作,三種狀態:pending(進行中)、fulfilled(已成功)和rejected(已失敗)。只有異步操作的結果,可以決定當前是哪一種狀態,任何其他操作都無法改變這個狀態。

(2)一旦狀態改變,就不會再變,任何時候都可以得到這個結果。Promise對象的狀態改變,只有兩種可能:從pending變爲fulfilled和從pending變爲rejected。只要這兩種情況發生,狀態就凝固了,不會再變了,至始至終會一直保持這個結果。

(3)Promise也有一些缺點。例如,無法取消Promise,一旦新建Promise對象它就會立即執行傳入的函數,無法中途取消。

注意,爲了方便理解(與 resolve 函數對應),統一使用 resolved 指代 fullfilled 狀態。

2. 基本用法

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

下面代碼創造了一個Promise實例。

注:文件讀寫是一個異步操作。

const fs = require('fs')

var promise =  new Promise(function (resolve, reject) {
        fs.readFile('./1.txt', 'utf-8', (err, data)=>{
            if(err)
                // 異步操作失敗:文件讀寫失敗 
                reject(err)
            else 
                // 異步操作成功:文件讀寫成功
                resolve(data)
        })
    })

2.1 新建Promise傳入的參數

Promise構造函數接受一個函數作爲參數,該函數的兩個參數分別是resolvereject。它們是兩個函數,由 JavaScript 引擎提供,不用自己部署。

resolve函數的作用是,將Promise對象的狀態從“未完成”變爲“成功”(即從 pending 變爲 resolved),在異步操作成功時調用,並將異步操作的結果,作爲參數傳遞出去,參數也可不傳。

reject函數的作用是,將Promise對象的狀態從“未完成”變爲“失敗”(即從 pending 變爲 rejected),在異步操作失敗時調用,並將異步操作報出的錯誤,作爲參數傳遞出去,參數也可不傳。

2.2 then方法的作用

Promise實例生成以後,可以用then方法分別指定resolved狀態和rejected狀態的回調函數,Promise 實例對象的 .then 方法中接收兩個參數,第一個參數是 Promise 對象由 pending 變成 resolved 時調用的回調函數 resolve,第二個參數是 Promise 對象由 pending 變成 rejected 狀態的時候調用的回調函數 reject。

promise.then(function(val){
    // promise 執行成功的回調函數的內容
},function(err){
    // promise 執行失敗的回調函數的內容
})

2.3 一個完整的例子

其中當前目錄下有一個文件 1.txt 其中的內容是 “111”

const fs = require('fs')

var promise =  new Promise(function (resolve, reject) {
	 
        console.log("我被立即執行了")
        fs.readFile('./1.txt', 'utf-8', (err, data)=>{
            if(err)
                // 異步操作失敗:文件讀寫失敗 
                reject(err)
            else 
                // 異步操作成功:文件讀寫成功
                resolve(data)
        })
    })

promise.then(function(val){
    console.log(val)
},function(err){
    console.log(err)
})

在上述代碼中,創建 promise 實例對象後,就會輸出“我被立即執行了”。

在 promise 對象中文件的讀取是異步操作,把它封裝在 promise 對象當中,then 方法指定的成功或者失敗時會執行的回調函數。

當readFile方法在執行的時候,由於是異步操作,結果不會立即返回,所以容器的狀態是pending。當文件讀取完成後,promise容器的狀態由 pending 變爲 resolved 或者 rejected,此時纔會執行相應的回調函數。

這裏文件讀取成功的結果會輸出 “111”(即執行 resolve 函數)若文件 1.txt 不存在則會報錯,“Error: ENOENT: no such file or directory“(即執行 reject 函數)。

3. Promise 的 then() 方法與鏈式調用

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

可以在 then 方法中傳入的回調函數返回一個新的 Promise 實例,這樣可以採用鏈式寫法,在 then 方法後可以再調用另外一個 then 方法,同時採用這樣寫法的第二個 Promise 實例會等待第一個 Promise 實例執行完對象內的異步操作執行回調函數結束後才執行,這樣就可以保證兩個或者多個異步操作可以按序執行。

const fs = require('fs')

function readFile(file){
	return new Promise(function (resolve, reject) {
        fs.readFile(file, 'utf-8', (err, data)=>{
            if(err)
                reject(err)
            else 
                resolve(data)
        })
    })
}

readFile("1.txt").then(data => {
	console.log(data);
	return readFile("2.txt")
}).then(data=>{
	console.log(data)
})

// 111
// 222

4. Promise 的 catch() 方法

Promise 對象的 then 方法中可以不指定異步操作發生錯誤的 rejectd 狀態下的回調函數,可以在一系列 Promise 對象鏈式調用的最後使用 catch 方法來捕捉 Promise 對象發生的錯誤。

readFile("1.txt").then(data => {
	
    console.log(data)
    return readFile("2.txt")

}).then(data=>{
	
    console.log(data)
    return readFile("3.txt")

}).then(data=>{
    
    console.log(data)

}).catch(err=>{
	
    console.log(err)

})
// catch 方法可以捕捉鏈式寫法中所有 promise 對象發生的錯誤
// 一旦在鏈中有 promise 對象發生錯誤,該鏈條後的所有 then 方法不再執行,轉向執行 catch 方法中的回調函數。

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

 

5. Promise.resolve() 和 Promise.reject() 方法

使用Promise原型對象中的 Promise.resolve() 方法可以把現有對象轉爲新的 Promise 對象,狀態爲resolve。

Promise.resolve('foo')

// 等價於

new Promise(resolve => resolve('foo'))

Promise.resolve() 方法的參數分爲以下四種情況。

5.1 參數是一個Promise實例

如果參數是一個 Promise 實例,那麼 Promise.resolve 將不做任何修改,原封不動地返回這個實例。

5.2 參數是一個帶有then方法的對象

例如下面的thenObj:

var thenObj = {
    then: function(resolve, reject){
        resolve(1);
    }
}

此時Promise.resolve() 方法會將這個對象轉爲 Promise 對象,然後立即執行 thenObj 對象的 then 方法。

5.3 參數不是具有then方法的對象或根本不是對象

如果參數是一個原始值,或者是一個不具有then方法的對象,那麼Promise.resolve方法返回一個新的Promise對象,狀態爲Resolved。

var p = Promise.resolve('Hello World');

p.then(function (s){
    console.log(s)
});

// 輸出 Hello World

5.4 不帶任何參數

Promise.resolve 方法允許在調用時不帶有參數,而直接返回一個 Resolved 狀態的 Promise 對象。

var p = Promise.resolve()

p.then(function(){
       // 要執行的代碼
});

5.5 Promise.reject() 

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

其餘用法與Promise.resolve的四個用法一致。

6. Promise.all() 和 Promise.race()

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

// p1、p1、p3均爲 promise 對象
const p = Promise.all([p1, p2, p3]);

上面代碼中,Promise.all方法接受一個數組作爲參數,p1p2p3都是 Promise 實例,如果不是,就會先調用上面講到的Promise.resolve方法,將參數轉爲 Promise 實例,再進一步處理。(Promise.all方法的參數可以不是數組,但必須具有 Iterator 接口,且返回的每個成員都是 Promise 實例。)

p的狀態由p1p2p3決定,分成兩種情況。

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

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

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

const p = Promise.race([p1, p2, p3]);

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

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

二、Promise對象的實現

大概理解了Promise 對象的使用方式,可以自己實現一個 Promise 來深入瞭解 Promise 對象的原理。

下面是我認爲寫的很好且容易讀懂的一段代碼,轉載自https://blog.csdn.net/yangbo1993/article/details/79034868

另外一篇寶藏文章:https://www.jianshu.com/p/43de678e918a

/**
 * 實現一個Promise
 */
function Promise(task) {
    //接收一個處理函數
    let that = this;//緩存this
    //promise有三種狀態  默認爲pending
    that.status = 'pending';
    that.onFulfilledFns = [];//所有成功的回調
    that.onRejectedFns = [];//所有失敗的回調
    that.value = undefined;
    function resolve(value) {
        //成功函數
        if(that.status == 'pending'){
            that.status = 'fulfilled';
            that.value = value;
            //執行所有成功的回調
            that.onFulfilledFns.forEach(item=>item(value));
        }
    };
    function reject(reason) {
        //失敗函數
        if(that.status == 'pending'){
            that.status = 'rejected';
            that.value = reason;
            //執行所有失敗的回到
            that.onRejectedFns.forEach(item=>item(reason));
        }
    };
    //立即執行傳入的處理函數
    try{
        task(resolve,reject);
    }catch (err){
        reject(err)
    }
};
function resolvePromise(promise2,x,resolve,reject) {
    let then;
    if(promise2 === x){
        return reject(new Error('循環引用'));
    }
    if(x instanceof Promise){
        //判斷x的prototype所指向的對象是否存在Promise的原型鏈上
        if(x.status= 'pending'){
            x.then(function (y) {
                //遞歸 調用
                resolvePromise(promise2,y,resolve,reject);
            },reject)
        }else if(x.status == 'fulfilled'){
            resolve(x.value);
        }else if(x.status == 'rejected'){
            reject(x.value);
        }
    }else if(x != null && typeof x == 'object' || typeof x == 'function'){
        try{
            then = x.then;
            if(typeof then == 'function'){
                then.call(x,function (y) {
                   resolvePromise(promise2,y,resolve,reject);
                },function (y) {
                    reject(y)
                });
            }
        }catch (e){
            reject(e);
        }
    }else{
        resolve(x);
    }
}
//then方法
Promise.prototype.then = function (onFulfilled, onRejected) {
    //假如沒有傳入異步處理程序則直接返回結果
    onFulfilled = typeof onFulfilled == 'function'?onFulfilled:function (value) {
        return value;
    };
    onRejected = typeof onRejected == 'function'?onRejected:function (reason) {
        return reason;
    };
    var promise2;//用來實現鏈式調用
    let that = this;
    if(that.status == 'fulfilled'){
        promise2 = new Promise(function (resolve,reject) {
            let x = onFulfilled(that.value);
            resolvePromise(promise2,x,resolve,reject);
        });
    }else if(that.status == 'rejected'){
        promise2 = new Promise(function (resolve,reject) {
            let x = onRejected(that.value);
            reject(x);
        });
    }else if(that.status == 'pending'){
        promise2 = new Promise(function (resolve,reject) {
            that.onFulfilledFns.push(function(){
                let x = onFulfilled(that.value);
                resolve(x);
            });
            that.onRejectedFns.push(function () {
                let x = onRejected(that.value);
                reject(x);
            });
        });
    }else{
        promise2 = new Promise(function (resolve,reject) {
            reject('Promise內部狀態錯誤');
        });
    }
    return promise2;
};
Promise.resolve = function (val) {
    return new Promise(function (resolve,reject) {
        resolve(val);
    });
};
Promise.reject = function (val) {
    return new Promise(function (resolve,reject) {
        reject(val);
    });
};
Promise.all = function (arrs) {
    //all方法接收一個promise數組,數組中所有異步操作結束後返回一個新的promise
    if(typeof arrs == 'object' && arrs.length > 0){
        return new Promise(function (resolve,reject) {
            let result = [];//新的promise返回結果
            let indexNum = 0;//當前完成幾個
            let resolved = function (index) {
                return function (data) {
                    result[index] = data;
                    indexNum++;
                    if(indexNum == arrs.length){
                        resolve(result);
                    }
                }
            };
            for(let i=0;i<arrs.length;i++){
                arrs[i].then(resolved(i),function (reason) {
                    reject(reason);
                });
            };
        });
    }else{
        return new Promise(function (resolve,reject) {
            reject(new Error('all方法傳入參數錯誤'));
        });
    }
};
Promise.race = function (arrs) {
    if(typeof arrs == 'object' && arrs.length > 0){
        return new Promise(function (resolve,reject) {
            for(let i=0;i<arrs.length;i++){
                arrs[i].then(function (data) {
                    resolve(data);
                },function (err) {
                    reject(err);
                });
            };
        });
    }else{
      return new Promise(function (resolve,reject) {
          reject(new Error('race方法傳入參數錯誤'));
      })
    };
};
 
module.exports = Promise;

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章