一文掌握Promise的用法

307 阅读7分钟

Promise作为在实际应用中或者面试中都经常出现的概念,掌握其基本用法很必要。后续会输出Promise相关的手写实现,那么我们就必须要掌握其方法的参数和返回值,此文按照阮一峰的ES6中关于Promise的讲解来整理!

理论简介

Promise:翻译过来就是"承诺"。是异步编程的解决方案,从语法上说,它是一个对象,是一个构造函数,用来生成实例。

Promise的两个特点:

  1. 对象的状态不受外界影响,只有异步操作的结果可以更改其状态。
  2. 状态一旦改变,则不会再次发生改变。也就是状态只能改变一次。

说到Promise的状态,他有三种状态:

  1. 进行中:pending
  2. 已成功:fulfilled
  3. 已失败:rejected

因为状态只可以改变一次,所以变化过程就只有如下两种情况:

  1. pending ->fulfilled
  2. pending->rejected

只要发生以上任一种情况,状态就凝固了不会再次变化,会一直保持这个结果,我们称这个状态为resolved(已定型)。

Promise的缺点:

  1. 无法取消Promise,新建后就会执行,无法中途取消
  2. 如果不设置回调函数,Promise内部发生的错误,不会反应到外部。也就是无法在try...catch(err){}中捕获到错误
  3. 当处于pending状态的时候无法得知目前在哪一个状态

基本用法

分析用法,主要从入参和返回值入手。

Promise对象作为构造函数,接受一个函数作为参数,该函数的两个参数分别是resolve和reject。这两个参数是函数类型,是javascript引擎提供的,不用自己部署,作用是更改Promise的状态。Promise新建后就会立即执行

resolve:在操作成功时调用。将状态从"未完成"->"完成",即pending->fulfilled;

reject:在操作失败时调用。将状态从"未完成"->"失败",即pending->rejected;

一般来讲,调用resolve或者reject之后并不会阻断后续代码的执行,但是从设计上来讲,后续的代码应该放到.then中执行,所以,最好在resolve或者reject的前面加上return语句。

//生成Promise实例
const promise = new Promise(function(resolve,reject){
    //逻辑代码
    if(异步操作成功){
        resolve(value)
    }else{
        reject(reason)
    }
})

Promise.prototype.then

入参:(成功状态下的处理函数,失败状态下的处理函数)

**返回值:**新的Promise实例。

Promise的实例生成后,可以用then方法分别指定“完成”状态和“失败”状态的回调函数,这两个函数都接受Promise对象传出的值作为参数。then方法返回一个新的Promise实例。

promise.then(function(value){
    //成功状态下的处理函数
    //如果resolve时传递出来的参数value是正常的值,那么这里接受到的就是正常的值,
    //但是如果是另外一个Promise实例的话,事情会是另外的样子,见下文
},function(reason){
    //失败状态下的处理函数
    //reject一般会传递Error对象的实例
});

如上文所提,如果在resolve时,传递给then的参数是另一个Promise实例,会发生什么呢?

const p1 = new Promise(function (resolve, reject) {    
    setTimeout(() => reject(new Error('fail')), 3000)
})
const p2 = new Promise(function (resolve, reject) {    
    setTimeout(() => resolve(p1), 1000)
})
p2.then(    
    result => console.log('success',result),
    reason => console.log('failed',reason)
)

是的,打印出来的是failed Error: fail!做对了吗?因为如果resolve方法将另一个Promise实例,也就是p1,作为返回值,那么p1的状态就会传递给p2,也就是说p1的状态决定了p2的状态。这里需要注意一下,如果是p2执行reject的时候返回了另一个Promise实例,则不会出现这种现象。

因为then方法返回的仍旧是一个Promise实例,因此then可以采用链式调用。前一个then的返回结果,将会被作为参数传入到下一个then的回调函数中。如果前一个then返回的还是Promise对象,那么下一个then将会等待其状态变化,然后根据其状态结果来决定是执行resolved还是rejected的回调函数。

Promise.prototype.catch

入参:(处理错误的函数)

**返回值:**新的Promise实例

**应用场景:**Promise内部运行中抛出错误 或者 调用reject。

“Promise内部会吃掉错误”就是我们上文提到过Promise内部发生的错误,不会反应到外部。也就是Promise内部的逻辑如果发生错误,Promise外部的逻辑会继续执行,并不会退出进程。并且Promise的错误并不会被try/catch处理到。如果要处理Promise中发生的错误,推荐的做法就是.catch。

Promise对象的错误具有“冒泡”性质,会一直向后传递,直到其被捕获为止。

Promise.prototype.finally

**入参:**无

**返回值:**原来的值

不管Promise的状态是什么,都会执行。

Promise.all

入参:([p1,p2,p3]),接受一个具有Iterator接口的数据作为参数,常用数组,且返回的每个成员必须是Promise实例。如果不是,就先调用Promise.resolve()将参数转为Promise实例。

**返回值:**新的promise实例

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

p的状态由传入的参数数组决定,分为两种情况:

  1. 数组中所有的实例的状态都变成fulfilled,p的状态才会变成fulfilled。此时p的回调函数then接受的参数是所有实例的返回值组成的数组。此处有一个注意点是:如果作为参数的实例自己定义了catch方法,那么即使此实例被rejected也不会触发Promise.all的catch方法。因为定义了catch之后,该方法返回的也是一个新的Promise实例,执行接受后,该实例的状态也是resolved状态的。
  2. 数组中只要有一个实例的状态变成了rejected,那么p的状态就会变成rejected。此时p的回调函数then接受的参数是第一个被reject的实例的返回值。

Promise.race

入参:([p1,p2,p3])同Promise.all

**返回值:**新的Promise实例

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

参数中的实例只要有一个率先返回状态,p的状态就随之改变。而那个率先改变的Promise实例的返回值就会被传递到p的回调函数then中。

Promise.allSettled

入参:([p1,p2,p3])同Promise.all

**返回值:**新的Promise实例

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

参数中所有的实例,不论成功与否均返回结果后,p的状态才发生改变,且状态总是fulfilled。p的回调函数then接受到的参数是一个数组,数组中每个成员都是对象。每个对象都有一个status属性,该属性的值只可能是字符串'fulfilled'或者字符串'rejected'。fulfilled时对象有value属性,rejected时有reason属性,分别对应两种状态的返回值。

Promise.allSettled 对于那种只关心所有实例是否执行完毕,而并不关心执行结果的情况非常有用。

Promise.any

入参:([p1,p2,p3])同Promise.all

**返回值:**新的Promise实例

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

只要有一个实例变成了fulfilled状态,那么p的状态就变成了fulfilled状态。

如果所有的实例都变成了rejected状态,那么p的状态就变成了rejected状态。

Promise.any 抛出的错误是一个AggregateError 实例。

Promise.resolve

**入参:**分为四种情况

  1. 没有参数:直接返回一个resolved状态的Promise对象
  2. 参数是一个Promise实例:不做任何修改,原地返回
  3. 参数是一个具有then方法的对象:会把这个对象转为Promise对象,并立刻执行这个then方法。运行结束后Promise对象的状态由这个then方法中的执行结果决定。如果有指定运行resolve或者reject,则Promise的状态变为指定的状态,否则一直是pending状态。
  4. 参数为其他对象或者不是对象:直接返回一个resolve状态的Promise对象。同时传入的参数会作为参数传递给Promise对象的回调函数.then中去。

Promise.reject

**入参:**不限

**返回值:**状态为rejected的新的Promise实例

Promise.reject()的参数会直接作为.then的返回值。