Promise系列一 - 认识Promise学会使用(resolve参数、对象/类方法)

224 阅读11分钟

一、传统异步请求的处理方式

这种回调的方式有很多的弊端:

  • 如果是我们自己封装的requestData,那么我们在封装的时候必须要自己设计好回调函数、回调函数的名称、回调函数的使用;
  • 如果我们使用的是别人封装的requestData或者一些第三方库, 那么我们必须去看别人的源码或者文档, 才知道它这个函数需要怎么去获取到结果、这个函数到底怎么用;
function requestData(url, sucessCallback, failtrueCallback) {
  setTimeout(() => {
  
    if (url === 'yzh') {
      const list = ['ace', 'sabot', 'luffy'];
      sucessCallback(list);
    } else {
      const message = '请求失败!';
      failtrueCallback(message);
    }
    
  }, 2000)
};

调用成功/失败结果:

requestData('yzh', res => {
  console.log(res); //[ 'ace', 'sabot', 'luffy' ]
}, err => {
  console.log(err);
});

requestData('abc', res => {
  console.log(res);
}, err => {
  console.log(err); //请求失败!
});

二、什么是Promise?

  • Promise是一个类,可以翻译成 许诺、承诺、答应、保证;
  • 当我们需要给予调用者一个承诺:待会儿我会给你回调数据时,就可以创建一个Promise的对象;
  • 在通过new创建Promise对象时,我们需要传入一个回调函数,这个函数被称之为"executor";
    • 这个回调函数会被立即执行,并且被传入另外两个回调函数resolve、reject;
    • 当我们调用resolve回调函数时,会执行Promise对象的then方法传入的回调函数;
    • 当我们调用reject回调函数时,会执行Promise对象的catch方法传入的回调函数;

2.1. promise的三种状态

TIP:

Promise状态一旦确定下来, 那么就是不可更改的(锁定);

在我们调用resolve的时候,那么会将该Promise的状态变成 兑现(fulfilled);

在之后我们去调用reject时,已经不会有任何的响应了(并不是这行代码不会执行,而是无法改变Promise状态);

  • 待定(pending):当执行executor中的代码时,处于该状态;
  • 已兑现(fulfilled):意味着操作成功完成,执行了resolve时,处于该状态;
  • 已拒绝(rejected):意味着操作失败,执行了reject时,处于该状态;
new Promise((resolve, reject) => {
  // pending
  console.log("pending中----------")
  
  resolve() // fulfilled
  reject() // rejected
  
  console.log("++++++++++++")
}).then(res => {
  console.log(res)
}, err => {
  console.log(err)
})

三、 promise异步请求的处理方式

  • resolve: 回调函数, 在成功时, 回调resolve函数;
  • reject: 回调函数, 在失败时, 回调reject函数;
function requestData(url) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      if (url === 'yzh') {
        const list = ['ace', 'sabot', 'luffy'];
        resolve(list);
      } else {
        const message = '请求失败!';
        reject(message);
      }
    }, 2000)
  })
};

then方法传入的两个回调函数:

  • 第一个回调函数, 会在Promise执行resolve函数时, 被回调
  • 第二个回调函数, 会在Promise执行reject函数时, 被回调
const promise = requestData('yzh');

promise.then(res => {
  console.log('请求成功:', res); //请求成功:['ace', 'sabot', 'luffy']
}, err => {
  console.log('请求失败:', err);
});

catch方法传入的回调函数, 会在Promise执行reject函数时, 被回调

const promise = requestData('abc');

promise.then(res => {
  console.log('请求成功:', res);
}, err => {
  console.log('请求失败:', err); //请求失败: 请求失败!
});

promise.catch(err => {
  console.log('catch:', err); //catch:  请求失败!
});

四、resolve参数

4.1. 普通的值或对象

  • 如果resolve传入一个普通的值或者对象,那么这个值会作为then回调的参数;
  • 状态是:pending -> fulfilled / pending -> rejected;
    new Promise((resolve, reject) => {
      resolve({ name: 'yzh', age: 18 });
      // reject('失败');
    }).then(res => {
      console.log('res:', res); // res: { name: 'yzh', age: 18 }
    }, err => {
      console.log('err:', err); // err: 失败
    });
    

4.2. 传入一个Promise

  • 如果resolve中传入的是另外一个Promise,那么这个新Promise会决定原Promise的状态;
  • 当前的Promise的状态会由传入的Promise来决定,相当于状态进行了移交;
    const newPromise = new Promise((resolve, reject) => {
      resolve('pending -> fulfilled');
      // reject('失败');
    });
    
    new Promise((resolve, reject) => {
      resolve(newPromise);
    }).then(res => {
      console.log('res:', res); // res:pending -> fulfilled
    }, err => {
      console.log('err:', err); // err: 失败
    });
    

4.3. 传入thenable对象

  • 如果resolve中传入的是一个对象,并且这个对象有实现then方法,那么会执行该then方法,并且根据then方法的结果来决定Promise的状态;
    new Promise((resolve, reject) => {
      resolve({
        then: function(resolve, reject) {
          resolve('thenable - ok');
          // reject('thenable - no');
        }
      });
    }).then(res => {
       console.log('res: ', res) // res:thenable - ok
    }, err => {
       console.log('err: ', err) // err:  thenable - no
    });
    
    

4.5. 补充

4.5.1 转成Promise对象

function foo() {
  const obj = { name: 'joo', age: 18 };
  
  return new Promise(resolve => {
    resolve(obj);
  });
};

foo().then(res => {
  console.log(res); //  { name: 'joo', age: 18 }
});

4.5.2 resolve方法

  • Promise.resolve的用法相当于new Promise,并且执行resolve操作;
    const prme = Promise.resolve({ name: 'kll' });
    //相当于
    /* const prme = new Promise(resolve => {
        resolve({ name: 'kll' });
    }); */
    
    prme.then(res => {
      console.log(res) // { name: 'kll' }
    });
    

4.5.3 reject方法

  • reject方法类似于resolve方法,只是会将Promise对象的状态设置为reject状态;
  • Promise.reject的用法相当于new Promise,并且执行reject操作;
    const promise = Promise.reject("rejected message");
    // 相当于
    /* const promise2 = new Promsie((resolve, reject) => {
      reject("rejected message");
    }); */
    
  • Promise.reject传入的参数无论是什么类型,都会直接作为reject状态的参数传递到catch的;
    // 注意: 无论传入什么值都是一样的
    const promise = Promise.reject(new Promise(() => {}));
    
    promise.then(res => {
      console.log("res:", res)
    }).catch(err => {
      console.log("err:", err) //err: Promise { <pending> }
    });
    

warning:不要在解析为自身的 thenable 上调用Promise.resolve。这将导致无限递归,因为它试图展平无限嵌套的 promise;

例如下例代码

let thenable = {
  then: (resolve, reject) => {
    resolve(thenable);
  }
};

Promise.resolve(thenable); //这会造成一个死循环

五、对象方法

TIP:

查看Promise有哪些对象方法: console.log(Object.getOwnPropertyDescriptors(Promise.prototype))

then、catch、finally方法是Promise对象上的方法:

它们其实是放在Promise的原型上的 Promise.prototype.then/catch/finally

Object.getOwnPropertyDescriptors()方法用来获取一个对象的所有自身属性的描述符;

5.1. then方法

前面:

const promise = new Promise((resolve, reject) => {
  resolve("hahaha");
  //reject("henghengheng");
});

5.1.1 接受两个参数

  • fulfilled的回调函数:当状态变成fulfilled时会回调的函数;
  • rejected的回调函数:当状态变成rejected时会回调的函数;
    promise.then(res => {
      console.log('res: ', res)
    }, err => {
      console.log('err: ', err)
    });
    
    //等同于
    promise.then(res => {
      console.log('res: ', res)
    }).catch(err => {
      console.log('err: ', err)
    });
    

5.1.2 多次调用

  • 同一个Promise可以被多次调用then方法
    • 每次调用我们都可以传入对应的fulfilled回调;
    • 当前面我们的resolve方法被回调时,所有的then方法传入的fulfilled回调函数都会被调用;
    promise.then(res => {
      console.log("res1:", res) //res1: hahaha
    });
    
    promise.then(res => {
      console.log("res2:", res) //res2: hahaha
    });
    
    promise.then(res => {
      console.log("res3:", res) //res3: hahaha
    });
    

5.1.3 返回值

  • then方法本身也是有返回值的, 它的返回值是Promise,所以我们可以进行链式调用;
  • then方法返回的Promise的三种状态;
    • 当then方法中的回调函数本身在执行的时候,那么它处于pending状态;
    • 当then方法中的回调函数返回一个结果时,那么它处于fulfilled状态,并且会将结果作为resolve的参数;
      • 1> 如果我们返回的是一个普通值(数值/字符串/普通对象/undefined), 那么这个普通的值被作为一个新的Promise的resolve值;
        promise.then(res => {
          console.log('res: ', res); //res:  hahaha
          return "aaaaaa"
        }).then(res => {
          console.log("res: ", res); //res:  aaaaaa
          return "bbbbbb"
        }).then(res => {
          console.log('res: ', res); //res:  bbbbbb
        })
        
      • 2> 如果我们返回的是一个Promise;
        promise.then(res => {
          return new Promise((resolve, reject) => {
            setTimeout(() => {
              resolve(111111)
            }, 2000)
          })
        }).then(res => {
          console.log("res: ", res) //res:  111111
        })
        
      • 3> 如果返回的是一个对象, 并且该对象实现了thenable;
        promise.then(res => {
          return {
            then: function(resolve, reject) {
              resolve(222222)
            }
          }
        }).then(res => {
          console.log("res: ", res) //res:  222222
        });
        
    • 当then方法抛出一个异常时,那么它处于rejected状态;
      promise.then(res => {
        throw new Error('error message!');
      }).catch(err => {
        console.log(err); //Error: error message!
      });
      

5.2. catch方法

5.2.1 多次调用

  • 一个Promise的catch方法是可以被多次调用的:
    • 每次调用我们都可以传入对应的rejected回调;
    • 当Promise的状态变成rejected的时候,这些回调函数都会被执行;
     const promise = new Promise((resolve, reject) => {
       reject('rejected status');
     });
    
     promise.catch(err => {
       console.log('err: ', err) //err:  rejected status
     });
     promise.catch(err => {
       console.log('err: ', err) //err:  rejected status
     });
    

5.2.2 抛出异常

前面:

const promise = new Promise((resolve, reject) => {
  // 1.
  // reject('reject status fn ');
  // throw new Error('reject status throw ');
  // 2.
  resolve();
});
  • 当executor抛出异常时,也是会调用错误捕获的回调函数的;
    // 1.
    promise.then(undefined, err => {
      console.log('err:', err); //err: reject status fn
    });
    
  • 通过catch方法来传入错误(拒绝)捕获的回调函数;
    // 1.
    promise.catch(err => {
      console.log('err:', err);
    });
    
  • 捕获级(优先捕获promise这个对象里的异常,无则去then里捕获异常);
    // 2.
    promise.then(res => {
      throw new Error('then throw error')
    }).catch(err => { 
      console.log('err:', err); //err: Error: then throw error
    });
    

5.2.3 返回值

  • 实际上catch方法也是会返回一个Promise对象的,所以catch方法后面我们可以继续调用then方法或者catch方法;
    const promise3 = new Promise((resolve, reject) => {
      reject('2222')
    });
    
    promise3.then(res => {}).catch(err => { 
      console.log('err', err); //err 2222
    
      return 'catch return value';
      // 等同于 
      // return new Promise(resolve => resolve('catch return value'));
      // 所以返回值会走then;
    
      // 除非返回new promise使用reject或者抛出异常会走到catch;
      // return new Promise((resolve, reject) => reject('new error'));
      // throw new Error('throw new err');
    }).then(res => {
      console.log('res2', res); //res2 catch return value
    }).catch(err => {
      // console.log('err2', err);  //err2 new error
      console.log('err2', err);  //err2 Error: throw new err
    });
    

5.3. finally方法

TIP:

这是为在Promise是否成功完成后都需要执行的代码提供了一种方式,避免了同样的语句需要在then()和catch()中各写一次的情况;

  • finally可以翻译成 最后、最终;
  • finally是在ES9(ES2018)中新增的一个特性:表示无论Promise对象无论变成fulfilled还是rejected状态,最终都会被执行的回调函数;
  • finally方法是不接收参数的,因为无论前面是fulfilled状态,还是rejected状态,它都会执行;
    const promise = new Promise((resolve, reject) => {
      // resolve('resolve message');
      reject('reject status');
    });
    
    promise.then(res => {
      console.log(res);
    }).catch(err => {
      console.log(err);
    }).finally(() => {
      console.log('无论resolve还是reject都会执行');
    });
    

🍚还有

六、类方法

前面写过的then、catch、finally方法都属于Promise的实例方法,都是存放在Promise的prototype上的;

resolve、reject是属于Promise的类方法(类的静态方法);

6.1. all方法

  • 它的作用是将多个Promise包裹在一起形成一个新的Promise;
  • 新的Promise状态由包裹的所有Promise共同决定:
    • 当所有的Promise状态变成fulfilled状态时,新的Promise状态为fulfilled,并且会将所有Promise的返回值组成一个数组;
      const proRe1 = new Promise((resolve, reject) => {
        setTimeout(() => {
          resolve('1111')
        }, 1000)
      });
      
      const proRe2 = new Promise((resolve, reject) => {
        setTimeout(() => {
          resolve('222')
        }, 2000)
      });
      
      const proRe3 = new Promise((resolve, reject) => {
        setTimeout(() => {
          resolve('3333')
        }, 3000)
      });
      
      Promise.all([proRe1, proRe2, proRe2]).then(res => {
        console.log(res); //[ '1111', '222', '3333' ]
      }).catch(err => {
        console.log(err);
      });
      
    • 当有一个Promise状态为rejected时,新的Promise状态为rejected,并且会将第一个reject的返回值作为参数;
      const proJe1 = new Promise((resolve, reject) => {
        setTimeout(() => {
          reject('1111')
        }, 1000)
      });
      
      const proJe2 = new Promise((resolve, reject) => {
        setTimeout(() => {
          reject('222')
        }, 2000)
      });
      
      const proJe3 = new Promise((resolve, reject) => {
        setTimeout(() => {
          resolve('3333')
        }, 3000)
      });
      
      Promise.all([proJe1, proJe2, proJe3]).then(res => {
        console.log(res);
      }).catch(err => {
        console.log(err); //1111
      });
      

6.2. allSettled方法

all方法缺陷:

当有其中一个Promise变成rejected状态时,新Promise就会立即变成对应的rejected状态;

那么对于resolve的,以及仍然处于pending状态的Promise,我们是获取不到对应的结果的;

  • 在ES11(ES2020)中,添加了新的API Promise.allSettled:
    • 该方法会在所有的Promise都有结果(settled),无论是fulfilled,还是reject时,才会有最终的状态;
    • 并且这个Promise的结果一定是fulfilled的(最终都会走到then);
      const proAllS1 = new Promise((resolve, reject) => {
        setTimeout(() => {
          resolve('1111')
        }, 1000)
      });
      
      const proAllS2 = new Promise((resolve, reject) => {
        setTimeout(() => {
          reject('222')
        }, 2000)
      });
      
      const proAllS3 = new Promise((resolve, reject) => {
        setTimeout(() => {
          resolve('3333')
        }, 3000)
      });
      
      Promise.allSettled([proAllS1, proAllS2, proAllS3]).then(res => {
        console.log('res: ', res);
        /*
        res:  [
                { status: 'fulfilled', value: '1111' },
                { status: 'rejected', reason: '222' },
                { status: 'fulfilled', value: '3333' }
              ]
        */
      }).catch(err => {
        console.log('err: ', err);
      });
      
  • allSettled的结果是一个数组,数组中存放着每一个Promise的结果,并且是对应一个对象的;
  • 这个对象中包含status状态,以及对应的value值;

6.3. race方法

  • race是比赛,竞争,角逐的意思,表示多个Promise相互竞争,谁先有结果,那么就使用谁的结果;
  • 如果有一个Promise有了结果,我们就希望决定最终新Promise的状态,那么可以使用race方法:
    const proRa1 = new Promise((resolve, reject) => {
      setTimeout(() => {
        resolve('1111');
      }, 1000)
    });
    
    const proRa2 = new Promise((resolve, reject) => {
      setTimeout(() => {
        reject('222');
      }, 2000)//500
    });
    
    const proRa3 = new Promise((resolve, reject) => {
      setTimeout(() => {
        resolve('3333');
      }, 3000)
    });
    
    //race是竞技、竞赛的意思,表示多个Promise相互竞争,谁先有结果,那么就使用谁的结果;
    Promise.race([proRa1, proRa2, proRa3]).then(res => {
      console.log('res: ', res); //res:  1111
    }).catch(err => {
      console.log('err: ', err);
    });
    

6.4. any方法

  • any方法是ES12(2021)中新增的方法,和race方法是类似的;
    • any方法会等到一个fulfilled状态,才会决定新Promise的状态;
      const p1 = new Promise((resolve, reject) => {
        setTimeout(() => {
          resolve('1111')
        }, 1000)
      });
      
      const p2 = new Promise((resolve, reject) => {
        setTimeout(() => {
          reject('222')
        }, 500)
      });
      
      const p3 = new Promise((resolve, reject) => {
        setTimeout(() => {
          resolve('3333')
        }, 3000)
      });
      Promise.any([p1, p2, p3]).then(res => {
        console.log('res: ', res); //res:  1111
      }).catch(err => {
        console.log('err: ', err);
      });
      
    • 如果所有的Promise都是reject的,那么也会等到所有的Promise都变成rejected状态;
      • 如果所有的Promise都是reject的,那么会报一个AggregateError的错误;
        const p1 = new Promise((resolve, reject) => {
          setTimeout(() => {
            reject('1111')
          }, 1000)
        });
        
        const p2 = new Promise((resolve, reject) => {
          setTimeout(() => {
            reject('222')
          }, 500)
        });
        
        const p3 = new Promise((resolve, reject) => {
          setTimeout(() => {
            reject('3333')
          }, 3000)
        });
        
        Promise.any([p1, p2, p3]).then(res => {
          console.log('res: ', res);
        }).catch(err => {
          console.log('err meesage: ', err);
          //err meesage:  [AggregateError: All promises were rejected]
          //              [聚合错误:所有承诺都被拒绝]
        });
        

通过终端 node index.js 运行代码,其中Node版本14.及以下使用any方法,会报错:

TypeError: Promise.any is not a function

我使用的是16.版本,可以正常获取运行结果;

Promise.any() 方法依然是实验性的,尚未被所有的浏览器完全支持;

🍚还有

实现Promise中的类方法