我所理解的promise

  雖然項目中一直在用到promise,雖然之前也學習過promise,可是對於promise真的是沒有很好的學以至用,有時候看到別人用promise的時候也是一臉懵逼,因此就決定花點時間再來好好研究一下promise究竟是什麼?應該怎麼樣用?es6

一、什麼是promise?

  Promise 是異步編程的一種解決方案,使得執行異步操做變得像同步操做同樣。它能夠被當作一個容器,容器裏面是咱們沒法干預的,裏面保存着某個將來纔會結束的事件的結果。 Promise 對象用於表示一個異步操做的最終狀態(完成或失敗),以及該異步操做的結果值。編程

基本用法以下數組

let p=new Promise((resolve,reject)=>{
        resolve(123)//成功時執行resolve
        reject("出錯了")//失敗時執行reject
    })
    p.then(res=>{
        console.log(res);
    },error=>{
        console.log(error);
    })
    //123 由於狀態變成resolve後就不會在改變了,因此reject不會被執行。
複製代碼

  Promise 是一個構造函數, new Promise 返回一個 promise對象,Promise 對象是一個代理對象(代理一個值),被代理的值在Promise對象建立時多是未知的。構造函數接收一個excutor執行函數做爲參數, excutor有兩個函數形參resolve和reject,分別做爲異步操做成功和失敗的回調函數。但該回調函數並非當即返回最終執行結果,而是一個能表明將來出現的結果的promise對象。resolve和reject函數在調用promise對象時纔會被執行。promise

二、Promise對象的特色

一、對象的狀態不受外界影響。 Promise對象有三種狀態(resolve,reject,pending)。只有異步操做的結果,能夠決定當前是哪種狀態,任何其餘操做都沒法改變這個狀態。異步

  • pending: 初始狀態,既不是成功,也不是失敗狀態。
  • fulfilled: 意味着操做成功完成。
  • rejected: 意味着操做失敗。

二、狀態一旦發生改變,就不會再變,任什麼時候候均可以獲得這個結果。 Promise對象的狀態改變,只有兩種可能:從pending變爲fulfilled和從pending變爲rejected。異步編程

let p = new Promise((resolve, reject) => {
        console.log(7);
        setTimeout(() => {
            console.log(5);
            resolve(6);//不會再執行了。由於在setTimeout外面已經執行過resolve了,promise的狀態已經改變了。
        }, 0)
        resolve(1);
    });
    p.then((arg) => {
        console.log(arg);
    });
    //因此上面的輸出爲七、一、5
複製代碼

三、Promise的實例方法

 一、Promise.prototype.then(onFulfilled, onRejected)

  onFulfilled和onRejected分別是Promise實例對象 的成功和失敗狀況的回調函數。該方法返回一個新的 promise實例對象, 將以回調的返回值做爲resolve的參數。而且then方法能夠被同一個 promise 對象調用屢次。函數

var promise1 = new Promise(function(resolve, reject) {
      resolve('Success!');
    });
    
    promise1.then(function(value) {
      console.log(value);//"Success!"
    });
複製代碼

  一、若是傳入的 onFulfilled 參數類型不是函數,則會在內部被替換爲(x) => x ,即原樣返回 promise 最終結果的函數。post

var p = new Promise((resolve, reject) => {
        resolve('foo')
    })
    
    // 'bar' 不是函數,會在內部被替換爲 (x) => x
    p.then('bar').then((value) => {
        console.log(value) // 'foo'
    })
    //等價於
    p.then(res=>{
        return res;
    }).then((value) => {
        console.log(value) // 'foo'
    })
複製代碼

  二、then方法容許鏈式調用。經過return將結果傳遞到下一個then,或者經過在then方法中新建一個promise實例,以resolve(value)或者reject(value)方法向下一個then方法傳遞參數學習

//例子1
    Promise.resolve("foo")
    .then(function (string) {
        return string;
    })
    .then(function (string) {
        setTimeout(function () {
            string += 'baz';
            console.log(string + "第二次調用");//foobaz第二次調用
        }, 1)
        return string;
    })
    .then(function (string) {
        console.log(string + "第三次調用");//foo第三次調用
        return string;
    }).then(res => {
        console.log(res + "第四次調用");//foo第四次調用
    });
    //例子2
    Promise.resolve("foo")
      .then(function(string) {
        return new Promise(function(resolve, reject) {
          setTimeout(function() {
            string += 'bar';
            resolve(string);
          }, 1);
        });
      })
      .then(function(string) {
        setTimeout(function() {
          string += 'baz';
          console.log(string);//foobaz
        }, 1)
      })
複製代碼

  三、若是函數拋出錯誤或返回一個rejected的Promise,則調用將返回一個rejected的Promise。this

//例子1
     Promise.resolve()
      .then( () => {
        // 使 .then() 返回一個 rejected promise
        throw 'Oh no!';
      })
      .then( () => {
        console.log( 'Not called.' );
      }, reason => {
        console.error( 'onRejected function called: ', reason );
        //onRejected function called:  Oh no!
    });
    //例子2
    Promise.reject()
      .then( () => 99, () => 42 )
      .then( solution => console.log( 'Resolved with ' + solution ) ); // Resolved with 42 //由於此時then方法接收的是上一個then方法reject方法中reject方法返回的值。
複製代碼

  四、 promise 的 .then 或者 .catch 能夠被調用屢次,但這裏 Promise 構造函數只執行一次。或者說 promise內部狀態一經改變,而且有了一個值,那麼後續每次調用 .then 或者 .catch 都會直接拿到該值。

const promise = new Promise((resolve, reject) => {
      setTimeout(() => {
        console.log('once');//once
        resolve('success')
      }, 1000)
    })
    
    const start = Date.now()
    promise.then((res) => {
      console.log(res, Date.now() - start);//success 1007
    })
    promise.then((res) => {
      console.log(res, Date.now() - start);//success 1007
    })
複製代碼

 二、Promise.prototype.catch(onRejected)

  該方法的原碼以下所示:
Promise.prototype.catch = function(onRejected) {
        return this.then(null, onRejected);
    }
    this.then(null, onRejected)中因爲null不爲函數,因此實際執行爲
    this.then(res=>{
        return res
    }, onRejected);
複製代碼

  該方法返回一個Promise,而且處理reject的的狀況。onRejected表示當Promise 被rejected時,被調用的一個Function。當這個回調函數被調用,新 promise 將以它的返回值來resolve下一個then函數繼續被調用(正常狀況是調用onFulfilled方法,除非在catch中拋出錯誤)。

//例子1
    var p1 = new Promise(function(resolve, reject) {
      resolve('Success');
    });
    
    p1.then(function(value) {
      console.log(value); // "Success!"
      throw 'oh, no!';
    }).catch(function(e) {
      console.log(e); // "oh, no!"
    }).then(function(){
      console.log('after a catch the chain is restored');//after a catch the chain is restored
      return 3;
    }, function () {
      console.log('Not fired due to the catch');
    })
複製代碼

  一、在異步函數中拋出的錯誤不會被catch捕獲到

var p2 = new Promise(function(resolve, reject) {
      setTimeout(function() {
        throw 'Uncaught Exception!';
      }, 1000);
    });
    
    p2.catch(function(e) {
      console.log(e); // 不會執行
    });
複製代碼

  二、在resolve()後面拋出的錯誤會被忽略(由於 Promise 的狀態一旦改變,就永久保持該狀態,不會再變了。)

var p3 = new Promise(function(resolve, reject) {
      resolve();
      throw 'Silenced Exception!';
    });
    
    p3.catch(function(e) {
       console.log(e); // 不會執行
    });
複製代碼

  三、Promise 對象的錯誤具備"冒泡"性質,會一直向後傳遞,直到被捕獲(或者被then()捕獲)爲止。也就是說,錯誤老是會被下一個catch語句捕獲。

Promise.reject(2).then((res) => {
        console.log(res);
    }).then().catch(res=>{
        console.log(res);//2
    });
    
    Promise.reject(2).then((res) => {
        console.log(res);
    },(error)=>{
        console.log(error);//2
    }).then().catch(res=>{
        console.log(res);//不會被捕獲,沒有任何輸出
    })
複製代碼

  四、若是使用了catch語句,而後前面的then方法並無報錯,那麼就至關於直接跳過該catch方法,下一個then方法接收上一個then方法中onFulfilled傳過來的參數。

var p1 = new Promise(function(resolve, reject) {
      resolve('Success');
    });
     p1.then(function(value) {
      console.log(value); // "Success"
      return value;
    }).catch(function(e) {
      console.log(e); // 沒有任何輸出
    }).then(function(value){
      console.log(value);//"Success" 
      //就好像跳過了catch語句,實際上catch執行的是this.then(null,onrejected)
      //等同於
      //this.then((res)=>{
      //   return res;
      //},onrejected)
    }, function () {
      console.log('Not fired due to the catch');
    })
複製代碼

五、.then 或者 .catch 中 return 一個 error 對象並不會拋出錯誤,因此不會被後續的 .catch 捕獲。由於返回任意一個非 promise 的值都會被包裹成 promise 對象,即 return new Error('error!!!') 等價於 return Promise.resolve(new Error('error!!!'))。

Promise.resolve()
      .then(() => {
        return new Error('error!!!')
        //或者改成
        //return Promise.reject(new Error('error!!!'))或者
       //throw new Error('error!!!') 纔會被後面的catch語句捕捉到
      })
      .then((res) => {
        console.log('then: ', res);//在這裏輸出
      })
      .catch((err) => {
        console.log('catch: ', err)
      })  
六、.then 或者 .catch 的參數指望是函數,傳入非函數則會發生值穿透。
        
    Promise.resolve(1)
    .then(2)
    .then(Promise.resolve(3))
    .then(console.log);
    //輸出1
    //實際執行語句爲
    new Promise((resolve, reject) => {
        resolve(1)
    }).then(res => {
        return res;
    }).then((res) => {
        return res;
    }).then((res) => {
        console.log(res);
    })
複製代碼

 三、Promise.prototype.finally(onFinally)

  該方法返回一個Promise,在promise執行結束時,不管結果是fulfilled或者是rejected,在執行then()和catch()後,都會執行finally指定的回調函數。源碼實現以下:
Promise.prototype.finally = function (f) {
      return this.then(function (value) {
        return Promise.resolve(f()).then(function () {
          return value;
        });
      }, function (err) {
        return Promise.resolve(f()).then(function () {
          throw err;
        });
      });
    };
複製代碼

三、Promise的靜態方法

 一、Promise.all(iterable)

  該方法返回一個新的promise對象,該promise對象在iterable參數對象裏全部的promise對象都成功的時候纔會觸發成功,一旦有任何一個iterable裏面的promise對象失敗則當即觸發該promise對象的失敗。

源碼實現以下:

Promise.al=function(promises){
        return new Promise(reslove,reject){
            let done=gen(promises.length;resolve);
            promises.forEach((promise,i)=>{
                promise.then(value=>{
                    done(i,value);
                },reject)
            })
        }
    }
    function gen(len,resolve){
        const count=0;
        let values=[];
        return function(i,value){
         values[i]  =value;
         if(++count===i){
             resolve(values);//將value構形成數據,最後一次調用resolve操做
         }
        }
    }
複製代碼

  操做成功(Fulfillment)

  一、若是傳入的可迭代對象爲空,Promise.all 會同步地返回一個已完成(resolved)狀態的promise。

var p = Promise.all([]);
    console.log(p);//Promise {<resolved>: Array(0)}
複製代碼

  二、若是全部傳入的 promise 都變爲完成狀態,或者傳入的可迭代對象內沒有 promise,Promise.all 返回的 promise 異步地變爲完成。

var p = Promise.all([]);
    console.log(p);
    //Promise {<pending>}
複製代碼

  三、在任何狀況下,Promise.all 返回的 promise 的完成狀態的結果都是一個數組,它包含全部的傳入迭代參數對象的值(也包括非 promise 值)。

var p1 = Promise.resolve(3);
    var p2 = 1337;
    var p3 = new Promise((resolve, reject) => {
      setTimeout(resolve, 100, 'foo');
    }); 
    
    Promise.all([p1, p2, p3]).then(values => { 
      console.log(values); // [3, 1337, "foo"] 
    });
複製代碼

  操做失敗(Rejection)

  一、若是傳入的 promise 中有一個失敗(rejected),Promise.all 異步地將失敗的那個結果給失敗狀態的回調函數,而無論其它 promise 是否完成。

var resolvedPromisesArray = [Promise.resolve(33), Promise.reject(44)];

    var p = Promise.all(resolvedPromisesArray);
    console.log(p);//Promise {<pending>}
    // using setTimeout we can execute code after the stack is empty
    //進入第二次循環
    setTimeout(function(){
        console.log('the stack is now empty');
        console.log(p);//Promise {<rejected>: 44}
    });
複製代碼

 二、Promise.race(iterable)

  原碼實現以下:
Promise.race = function(promises) {
        return new Promise((resolve, reject) => {
            promises.forEach((promise, index) => {
               promise.then(resolve, reject);
            });
        });
    }
複製代碼

  該方法返回一個 promise,一旦迭代器中的某個子promise執行了成功或失敗操做,父promise對象也會用子promise的成功返回值或失敗詳情做爲參數調用父promise綁定的相應句柄,並返回該promise對象。若是傳的迭代(iterable)是空的,則返回的 promise 將永遠等待。

let p=Promise.race([1,2]).then(value=>{
        console.log(value);//1
    });

    let p=Promise.race([]);
    console.log(p);//Promise {<pending>}
複製代碼

 三、Promise.resolve(value)

  該方法的做用就是將現有對象轉爲 Promise 實例,而且該實例的狀態爲resolve。 該方法的源碼實現以下:

Promise.resolve (value) {
      // 若是參數是MyPromise實例,直接返回這個實例
      if (value instanceof MyPromise) return value
      return new MyPromise(resolve => resolve(value))
    }
複製代碼

參數value能夠是一個Promise對象,或者是一個thenable,也能夠就是一個字符串等常量,還能夠爲空。該方法返回一個以給定值解析後的Promise 對象。

Promise.resolve('foo')
    // 等價於
    return new MyPromise(resolve => resolve(value))
    if (value instanceof MyPromise) {
        return value
    }else{
        return new MyPromise(resolve => resolve(value))
    }
複製代碼

  一、若是這個值是個thenable(即帶有then方法),返回的promise會"跟隨"這個thenable的對象(從它們的運行結果來看,返回的就好像是這個thenable對象同樣。但實際上返回的是一個Promise對象),採用它的最終狀態(指resolved/rejected/pending/settled)。

let thenable = {
      then: function(resolve, reject) {
        resolve(42);
      }
    };
    let p1 = Promise.resolve(thenable);
    p1.then(function(value) {
      console.log(value);  // 42
    });
    thenable.then(res=>{
        console.log(res);//42
    })
    
    //上面的代碼能夠被解析爲
    let p1=new Promise(resolve=>{
        reslove(thenable);
    })
    p1.then(function(value) {
      console.log(value);  // 42 
    });
複製代碼

  二、若是傳入的value自己就是promise對象,那麼Promise.resolve將不作任何修改、原封不動地返回這個實例。

var original = Promise.resolve('我在第二行');
    original.then(function(value) {
        console.log('value: ' + value);//'我在第二行'
    });
    var cast = Promise.resolve(original);
    cast.then(function(value) {
        console.log('value: ' + value);//'我在第二行'
    });
    console.log('original === cast ? ' + (original === cast));//true
複製代碼

  三、參數不是具備then方法的對象,或根本就不是對象,則Promise.resolve方法返回一個新的 Promise 對象,狀態爲resolved。

const p = Promise.resolve('Hello');
    //等同於
    const p=new Promise(resolve=>{
        resolve('Hello');
    })
    p.then(res=>{
      console.log(res) // Hello
    });
複製代碼

  四、不帶有任何參數。直接返回一個resolved狀態的 Promise 對象。

const p = Promise.resolve();
    console.log(p);//Promise {<resolved>: undefined}
複製代碼

 四、Promise.rejected(value)

  返回一個新的 Promise 實例,該實例的狀態爲rejected。
const p = Promise.reject('出錯了');
    // 等同於
    const p = new Promise((resolve, reject) => reject('出錯了'))
    p.then(null, function (s) {
      console.log(s);//  出錯了
    });
複製代碼

四、Promise代碼實現

最開始的基礎版
class Promise(){
        constructor(executor){
            this.status="pending";//初始狀態
            this.value=null;//resolve的實參
            this.reason=null;//reject的實參
            try{
               //防止發生隱式綁定 executor(this.resolve.bind(this),this.reject.bind(this));
            }catch(e){
                this.reject(e);
            }
        }
       
        resolve(value){
            let status=this.status;
            if(status=="pending"){
                this.status="resolve";
                this.value=value;
            }
        }
        reject(reason){
            let status=this.status;
            if(status=="pending"){
                this.status="reject";
                this.reason=reason;
            }
        }
    }
    Promise.then=(onfullFilled,onRejected)=>{
        if(this.status=="resolve"){
            onfullFilled(this.value);
        }
        if(this.status=="reject"){
            onfullFilled(this.reason);
        }
    }
複製代碼

進階版1 考慮異步狀況

class Promise(executor){
        constructor(executor){
            if(typeof executor !=="function"){
                throw new Error("executor必須是一個函數");
            }
            this.status="pending";//初始狀態
            this.value=null;//resolve的實參
            this.reason=null;//reject的實參
            this.fullFilledArray=[];
            that.rejectedArray=[];
             try{
               //防止發生隱式綁定
               
               executor(this.resolve.bind(this),this.reject.bind(this));
            }catch(e){
                this.reject(e);
            }
        }
        resolve(value){
            let status=this.status;
            if(status=="pending"){
                this.status="resolve";
                this.value=value;
                let cb;
                while (cb =  this.fullFilledArray.shift()) {
                  cb(this.value)
                }
               
            }
        }
        reject(reason){
            let status=this.status;
            if(status=="pending"){
                this.status="reject";
                this.reason=reason;
                let cb;
                while (cb =  this.rejectedArray.shift()) {
                  cb(this.reason)
                }
               
            }
        }
    }
    Promise.then=(onfullFilled,onRejected){
        let status=this.status;
        switch(status){
            case "pending":
                 //由於異步的緣由,在Promise的構造函數中的resolve和reject方法並不會立刻執行,等到調用this.then以後纔會去執行(`根據js的執行機制可知`)
                this.fullfilledArray.push(onfullFilled);
                this.rejectedArray.push(onRejected)
                break;
            case "resolve":
                this.onfullFilled(this.value);
                break;
            case "reject":
                this.onRejected(this.reason)
                break;
            
        }
    }
複製代碼

進階版2 考慮鏈式調用

class Promise(){
        constructor(executor){
            if(typeof executor !=="function"){
                throw new Error("executor必須是一個函數");
            }
            this.status="pending";//初始狀態
            this.value=null;//resolve的實參
            this.reason=null;//reject的實參
            this.fullFilledArray=[];
            this.rejectedArray=[];
             try{
               //防止發生隱式綁定 executor(this.resolve.bind(this),this.reject.bind(this));
            }catch(e){
                this.reject(e);
            }
        };
        resolve(value){
            let status=this.status;
            if(status==="pending"){
                this.status="resolve";
                this.value=value;
                let cb;
                while (cb =  this.fullFilledArray.shift()) {
                  cb(this.value)
                }
            }
        };
        reject(reason){
            let status=this.status;
            if(status==="pending"){
                this.status="rejected";
                this.reason=reason;
                let cb;
                while (cb =  this.rejectedArray.shift()) {
                  cb(this.reason)
                }
            }
        }
        
   }
   Promise.then=function(onFullFilled,onRejected){
       let {status,value,reason}=this;
       return new Promise((resolve,reject)=>{
           let fullfilled=value=>{
               try{
                   if(typeof onFullFilled !=="function"){
                        resolve(value);
                   }else{
                       let res=onFullFilled(value);
                       if(res instanceof Promise){
                           res.then(resolve,reject)
                       }else{
                           resolve(res);
                       }
                   }
               }catch(e){
                   reject(e);
               }
           };
           let rejected=reason=>{
               try{
                    if(typeof onRejected !=="function"){
                        reject(value);
                    }else{
                        let res=onRejected(value);
                        if(res instanceof Promise){
                            res.then(resolve,reject);
                        }else{
                            resolve(res);
                        }
                    }
               }catch(e){
                   reject(e);
               }
                
           };
            swich(status){
                case "panding":
                    this.fullFilledArray.push(fullfilled);
                    this.rejectedArray.push(rejected);
                    break;
                case "resolve":
                    fullfilled(value);
                    break;
                case "reject":
                    rejected(reason);
           }
       })
   }
複製代碼

}

後面再複雜的狀況暫時就不考慮了。

參考連接
一、ECMAScript入門
二、Promise——MDN
三、Promise原理講解 && 實現一個Promise對象 (遵循Promise/A+規範)
四、這一次,完全弄懂 JavaScript 執行機制
五、Promise 必知必會(十道題)

相關文章
相關標籤/搜索