我所理解的promise

755 阅读11分钟

  虽然项目中一直在用到promise,虽然以前也学习过promise,但是对于promise真的是没有很好的学以致用,有时候看到别人用promise的时候也是一脸懵逼,所以就决定花点时间再来好好研究一下promise到底是什么?应该怎么样用?

1、什么是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对象时才会被执行。

2、Promise对象的特点

1、对象的状态不受外界影响。 Promise对象有三种状态(resolve,reject,pending)。只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态。

  • pending: 初始状态,既不是成功,也不是失败状态。
  • fulfilled: 意味着操作成功完成。
  • rejected: 意味着操作失败。

2、状态一旦发生改变,就不会再变,任何时候都可以得到这个结果。 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);
        });
        //所以上面的输出为7、1、5(setTimeout是宏任务)

3、Promise的实例方法

 1、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!"
        });

  1、如果传入的 onFulfilled 参数类型不是函数,则会在内部被替换为(x) => x ,即原样返回 promise 最终结果的函数。

        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'
        })

  2、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);//foobarbaz
            }, 1)
          })

  3、如果函数抛出错误或返回一个rejected的Promise,则调用将返回一个rejected的Promise。

        //例子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方法返回的值。

  4、 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
        })

 2、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');
        })

  1、在异步函数中抛出的错误不会被catch捕获到

        var p2 = new Promise(function(resolve, reject) {
          setTimeout(function() {
            throw 'Uncaught Exception!';
          }, 1000);
        });
        
        p2.catch(function(e) {
          console.log(e); // 不会执行
        });

  2、在resolve()后面抛出的错误会被忽略(因为 Promise 的状态一旦改变,就永久保持该状态,不会再变了。)

        var p3 = new Promise(function(resolve, reject) {
          resolve();
          throw 'Silenced Exception!';
        });
        
        p3.catch(function(e) {
           console.log(e); // 不会执行
        });

  3、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);//不会被捕获,没有任何输出
        })

  4、如果使用了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');
        })

   5、.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)
          })  

  6、.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);
        })

 3、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;
            });
          });
        };

3、Promise的静态方法

 1、Promise.all(iterable)

  该方法返回一个新的promise对象,该promise对象在iterable参数对象里所有的promise对象都成功的时候才会触发成功,一旦有任何一个iterable里面的promise对象失败则立即触发该promise对象的失败。

源码实现如下:

        Promise.all=function(promises){
            return new Promise(reslove,reject){
                let arr=[];
                promises.forEach((promise,i)=>{
                    promise.then(value=>{
                        arr.push(value);
                        if(i===promises.length){
                            resolve(arr);
                        }
                    },reject)
                })
            }
        }

  操作成功(Fulfillment)

  1、如果传入的可迭代对象为空,Promise.all 会同步地返回一个已完成(resolved)状态的promise。

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

  2、如果所有传入的 promise 都变为完成状态,或者传入的可迭代对象内没有 promise,Promise.all 返回的 promise 异步地变为完成。   3、在任何情况下,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)

  1、如果传入的 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}
        });

 2、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>}
        

 3、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))
        }

  1、如果这个值是个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 
        });

  2、如果传入的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
        

  3、参数不是具有then方法的对象,或根本就不是对象,则Promise.resolve方法返回一个新的 Promise 对象,状态为resolved。

    const p = Promise.resolve('Hello');
    //等同于
    const p=new Promise(resolve=>{
        resolve('Hello');
    })
    p.then(res=>{
      console.log(res) // Hello
    });

  4、不带有任何参数。直接返回一个resolved状态的 Promise 对象。

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

 4、Promise.rejected(value)

  返回一个新的 Promise 实例,该实例的状态为rejected。
    const p = Promise.reject('出错了');
    // 等同于
    const p = new Promise((resolve, reject) => reject('出错了'))
    p.then(null, function (s) {
      console.log(s);//  出错了
    });

4、Promise代码实现

//初始版
    function Promise1(executor) {
        let self = this;
        self.status = "pending";
        self.value = undefined;
        self.reason = undefined;
        function resolve(value) {
          if (self.status === "pending") {
            self.value = value;
            self.status = "resolve";
          }
        }
        function reject(reason) {
          if (self.status === "pending") {
            self.reason = reason;
            self.status = "reject"
          }
        }
        try {
          executor(resolve, reject)
        } catch (e) {
          reject(e)
        }
    }
    Promise1.prototype.then = function (onFullfilled, onRejected) {
        let status = this.status;
        switch (status) {
          case "resolve":
            onFullfilled(this.value);
            break;
          case "reject":
            onRejected(this.reason);
            break;
          default:
            break;
        }
    }
    let p = new Promise1((resolve, reject) => {
        resolve(123);
    });
    p.then(res => {
        console.log(res);
    })
    //进阶版 处理异步
    function Promise2(executor) {
        let self = this;
        self.status = "pending";
        self.value = undefined;
        self.reason = undefined;
        self.onFullfilledArray = [];
        self.onRejectedArray = [];
        function resolve(value) {
          if (self.status === "pending") {
            self.value = value;
            self.status = 'resolve';
            self.onFullfilledArray.forEach(fn => {
              fn(self.value);
            })
          }
        }
        function reject(reason) {
          if (self.status === "pending") {
            self.reason = reason;
            self.status = "reject";
            self.onFullfilledArray.forEach(fn => {
              fn(this.reason);
            })
          }
        }
        try {
          executor(resolve, reject)
        } catch (e) {
          reject(e);
        }
    }
    Promise2.prototype.then = function (onFullfilled, onRejected) {
        let status = this.status;
        switch (status) {
          case "pending":
            this.onFullfilledArray.push(() => {
              onFullfilled(this.value);
            })
            this.onRejectedArray.push(() => {
              onRejected(this.reason);
            });
            break;
          case "resolve":
            onFullfilled(this.value);
            break;
          case "reject":
            onRejected(this.reason);
            break;
          default:
            break;
        }
    }
    let p = new Promise2((resolve, reject) => {
        setTimeout(function () { resolve(1) }, 1000)
    });
    p.then(res => {
        console.log(res);
    })
    //进阶2 链式调用
    function Promise3(executor) {
        if (typeof executor !== "function") {
          throw new Error("executor必须是一个函数");
        }
        let self = this;
        self.status = "pending";
        self.value = "";
        self.reason = "";
        self.onFullfilledArray = [];
        self.onRejectedArray = [];
        
        function resolve(value) {
          if (self.status === "pending") {
            self.status = "resolve";
            self.value = value;
            self.onFullfilledArray.forEach(fn => {
              fn(this.value);
            });
          }
        }
        function reject(reason) {
          if (self.status === "pending") {
            self.status = "resolve";
            self.reason = reason;
            self.onRejectedArray.forEach(fn => {
              fn(this.reason);
            });
          }
        }
        try {
          executor(resolve, reject)
        } catch (e) {
          reject(e);
        }
    }
    Promise3.prototype.then = function (onFullfilled, onRejected) {
        let status = this.status;
        let promise;
        switch (status) {
          case "pending":
            promise = new Promise3((resolve, reject) => {
              this.onFullfilledArray.push(() => {
                let value = onFullfilled(this.value);
                try {
                  resolve(value);
                } catch (e) {
                  reject(e);
                }
              });
              this.onRejectedArray.push(() => {
                let value = onRejected(this.reason);
                try {
                  resolve(value);
                } catch (e) {
                  reject(e);
                }
              });
            });
            break;
          case "resolve":
            promise = new Promise3((resolve, reject) => {
              let value = onFullfilled(this.value);
              try {
                resolve(value);
              } catch (e) {
                reject(e);
              }
            });
            break;
          case "reject":
            promise = new Promise3((resolve, reject) => {
              let value = onRejected(this.reason);
              try {
                resolve(value);
              } catch (e) {
                reject(e);
              }
            });
            break;
        }
        return promise;
    }

后面再复杂的情况暂时就不考虑了。

参考链接
1、ECMAScript入门
2、Promise——MDN
3、Promise原理讲解 && 实现一个Promise对象 (遵循Promise/A+规范)
4、这一次,彻底弄懂 JavaScript 执行机制
5、Promise 必知必会(十道题)