談談Promise

oOjia發表於2018-05-20

什麼是Prmoise

Promise 是非同步程式設計的一種解決方案,比傳統的解決方案—回撥函式和事件—更合理和更強大。它由社群最早提出和實現,ES6 將其寫進了語言標準,統一了用法,原生提供了Promise物件。

Promise物件可以理解為一次執行的非同步操作,使用promise物件之後可以使用一種鏈式呼叫的方式來組織程式碼;讓程式碼更加的直觀。也就是說,有了Promise物件,就可以將非同步操作以同步的操作的流程表達出來,避免了層層巢狀的回撥函式。

Promise物件的特點

1、物件的狀態不受外界影響。

Promise物件代表一個非同步操作,有三種狀態

  • pending(執行中)
  • Resolved(成功,又稱Fulfilled)
  • rejected(拒絕)

其中pending為初始狀態,fulfilled和rejected為結束狀態(結束狀態表示promise的生命週期已結束)。

promise只有非同步操作的結果,可以決定當前是哪一種狀態,任何其他操作都無法改變這個狀態。

2、一旦狀態改變,就不會再變,任何時候都可以得到這個結果。

Promise物件的狀態改變,只有兩種可能:從Pending變為Resolved和從Pending變為Rejected,只要這兩種情況發生,狀態就凝固了,不會再變了,會一直保持這個結果

Promise物件的缺點:

1、無法取消Promise,一旦新建它就會立即執行,無法中途取消。

2、如果不設定回撥函式,Promise內部丟擲的錯誤,不會反應到外部。

3、當處於Pending狀態時,無法得知目前進展到哪一個階段(剛剛開始還是即將完成)

如果某些事件不斷地反覆發生,一般來說,使用 Stream 模式是比部署Promise更好的選擇。

Promise的使用

1、基本用法:

(1)先new一個Promise,將Promise例項化

(2)然後在例項化的promise傳兩個引數,一個是成功之後的resolve,一個是失敗之後的reject

(3)Promise例項生成以後,可以用then方法分別指定Resolved狀態和Reject狀態的回撥函式。then方法可以接受兩個回撥函式作為引數。第一個回撥函式是Promise物件的狀態變為resolved時呼叫,第二個回撥函式是Promise物件的狀態變為rejected時呼叫。其中,第二個函式是可選的,不一定要提供。這兩個函式都接受Promise物件傳出的值作為引數。

var promise = function(isReady){
    return new Promise(function(resolve, reject){
        // do somthing, maybe async
        if (isReady){
          return resolve('hello world');
        } else {
          return reject('failure');
        }
    });
}
 
//Promise例項生成以後,可以用then方法分別指定Resolved狀態和Reject狀態的回撥函式。
promise(true).then(function(value){
    // success,這裡是resolve的回撥函式
    console.log(value);  //hello world
}, function(err){
    // failure,這裡是reject的回撥函式
    console.log(err)
})
複製程式碼

2、鏈式呼叫

then()方法的作用是Promise例項新增解決(fulfillment)和拒絕(rejection)狀態的回撥函式。then()方法會返回一個新的Promise例項,所以then()方法後面可以繼續跟另一個then()方法進行鏈式呼叫。

let p = new Promise((resolve, reject) => {
    setTimeout(resolve, 1000, 'success');
});
p.then(
    res => {
        console.log(res);
        return new Promise((resolve, reject) => {
            setTimeout(resolve, 1000, 'success');
        });
    }
).then(
    res => console.log(res)
);
// success
// 1000ms後
// success
複製程式碼

3、catch方法 其實它和then的第二個引數一樣,用來指定reject的回撥,不過它還有另外一個作用:在執行resolve的回撥(也就是上面then中的第一個引數)時,如果丟擲異常了(程式碼出錯了),那麼並不會報錯卡死js,而是會進到這個catch方法中。

getNumber()
.then(function(data){
    console.log('resolved');
    console.log(data);
    console.log(someData); //此處的someData未定義
})
.catch(function(reason){
    console.log('rejected');
    console.log(reason);
});
複製程式碼

如果我們不用Promise,程式碼執行到console就直接在控制檯報錯了,不往下執行了。但是在這裡,進到catch方法裡面去了,而且把錯誤原因傳到了reason引數中。即便是有錯誤的程式碼也不會報錯了,這與我們的try/catch語句有相同的功能。

4、all的用法

Promise.all方法用於將多個 Promise 例項,包裝成一個新的 Promise 例項。Promise.all可以接收一個元素為 Promise 物件的陣列作為引數,當這個陣列裡面所有的 Promise 物件都變為 resolve 時,該方法才會返回。(Promise.all方法的引數可以不是陣列,但必須具有 Iterator 介面,且返回的每個成員都是 Promise 例項。)

var p1 = new Promise(function (resolve) {
    setTimeout(function () {
        resolve("promise1");
    }, 2000);
});

var p2 = new Promise(function (resolve) {
    setTimeout(function () {
        resolve("promise2");
    }, 1000);
});

Promise.all([p1, p2]).then(function (result) {
    console.log(result); // ["promise1", "第二個promise2"]
});
複製程式碼

5、race的用法

promise.race也是傳入一個陣列,但是與promise.all不同的是,race只返回跑的快的值,也就是說result返回比較快執行的那個。

var p1 = new Promise(function (resolve) {
    setTimeout(function () {
        console.log(1);
        resolve("promise1");
    }, 2000);
});

var p2 = new Promise(function (resolve) {
    setTimeout(function () {
        console.log(2);
        resolve("promise2");
    }, 1000);
});

Promise.race([p1, p2]).then(function (result) {
    console.log(result); 
});

// 結果:
// 2
// promise2
// 1
複製程式碼

可以看到,傳的值中,只有p2的返回了,但是p1沒有停止,依然有執行。

race的應用場景為,比如我們可以設定為網路請求超時。寫兩個promise,如果在一定的時間內如果成功的那個我們沒有執行到,我們就執行失敗的那個.

手寫Promise

手寫實現Promise程式碼:

function resolvePromise(promise2,x,resolve,reject){
    //判斷x是否等於promise
    //promiseA+規定了一段程式碼,可以實現promise之間的互動
    if(promise2 === x){//不能自己等待自己完成
        return reject(new TypeError('迴圈引用'))
    }
    if(x != null && (typeof x === 'object' || typeof x === 'function')){
        //不是null 也不是物件和函式
        let called;//防止成功後再呼叫失敗
        try{//防止取then時報錯
            let then = x.then;
            if(typeof then === 'function'){//如果是函式就認為它是promise
                //call第一引數是this,後面是成功的和失敗的回撥
                then.call(x,y=>{
                    if(called)return;
                    called = true;
                    resolvePromise(promise2,y,resolve,reject)//遞迴
                },r=>{
                    if(called)return;
                    called = true;
                    reject(r)
                })
            }else{//then是個普通物件 直接成功即可
                resolve(x)
            }
        }catch(e){
            if(called)return;
            called = true;
            reject(e)
        }
    }else{//x就是個普通值
        resolve()
    }
}
class Promise{
    constructor(executor){
        this.status = 'pending';//預設等待狀態
        this.value = undefined;
        this.reason = undefined;
        this.onResolvedCb = [];//存放成功的回撥
        this.onRejectedCb = [];//存放失敗的回撥
        let resolve = (data) => {
            if(this.status === 'pending'){
                this.value = data;
                this.status = 'resolved';
                this.onResolvedCb.forEach(fn=>fn());//解決非同步問題
            }
        }
        let reject = (reason) => {
            if(this.status === 'pending'){
                this.reason = reason;
                this.status = 'rejected';
                this.onRejectedCb.forEach(fn=>fn());
            }
        }
        try{//執行時可能會發生異常
            executor(resolve,reject);
        }catch(e){
            reject(e)
        }
    }
    then(onFullFilled,onRejected){
        //then不傳參的時候
        onFullFilled = typeof onFullFilled === 'function' ? onFullFilled : y => y;
        onRejected = typeof onRejected === 'function' ? onRejected : err => { throw err };

        let promise2;//鏈式呼叫
        if(this.status === 'resolved'){
            // onFullFilled(this.value);
            promise2 = new Promise((resolve,reject)=>{
                try{
                    let x = onFullFilled(this.value);
                    //看x是不是promise 是promise或者是個普通值的話 作為promise2的成功結果  
                    resolvePromise(promise2,x,resolve,reject)        
                    //resolvePromise可以解析x和promise之間的關係   
                }catch(e){
                    reject(e)
                }
            })
            return promise2;//呼叫then後返回一個新的promise
        }
        if(this.status === 'rejected'){
            promise2 = new Promise((resolve,reject)=>{
                try{
                    let x = onFullFilled(this.reason);
                    resolvePromise(promise2,x,resolve,reject)  
                }catch(e){
                    reject(e)
                }
            })
            return promise2;
        }
        if(this.status === 'pending'){//既沒有成功也沒有失敗(先執行了then)
            promise2 = new Promise((resolve,reject)=>{
                this.onResolvedCb.push(()=>{//存放成功回撥
                    try{
                        //還可以做其他操作
                        let x = onFullFilled(this.value)
                        resolvePromise(promise2,x,resolve,reject)  
                    }catch(e){
                        reject(e)
                    }            
                })
                this.onRejectedCb.push(()=>{//存放失敗回撥
                    try{
                        //還可以做其他操作
                        let x = onRejected(this.reason)
                        resolvePromise(promise2,x,resolve,reject)
                    }catch(e){
                        reject(e)
                    }             
                })
            })
            return promise2;
        }
    }
    catch(onRejected){
        return this.then(null,onRejected);
    }
}
Promise.resolve = function(val){
    return new Promise((resolve,reject)=>resolve(val))
}
Promise.reject = function(val){
    return new Promise((resolve,reject)=>reject(val))
}
Promise.all = function(promises){
    return new Promise((resolve,reject)=>{
        let arr=[],i=0;
        function processData(index,data){
            arr[index]=data;
            i++;
            if(i ===promises.length){
                resolve(arr)
            }
        }
        for(let i=0;i<promises.length;i++){
            promises[i].then(data=>{
                processData(i,data)
            },reject)
        }
    })
}
Promise.race = function(promises){
    return new Promise((resolve,reject)=>{
        for (let i = 0; i < promises.length; i++) {
            promises[i].then(resolve,reject)
        }
    })
}
//promise 語法糖
Promise.deferred = Promise.defer = function () {
    let dfd = {};
    dfd.promise = new Promise((resolve,reject)=>{
      dfd.resolve = resolve;
      dfd.reject = reject;
    })
    return dfd;
  }
  // npm install promises-aplus-tests -g
  // promises-aplus-test 檔名
module.exports = Promise;
複製程式碼

相關文章