Promise的九大方法(resolve、reject、then、catch、finally、all、allSettled、race、any)你都用过那些?

简介: Promise的九大方法(resolve、reject、then、catch、finally、all、allSettled、race、any)你都用过那些?

Promise 状态

初始状态 -> pending

初始状态可以改变

在resolve 或者 reject 调用之前都处于这个状态

最终成功状态 -> fulfilled

执行 resolve 函数,状态改变为 fulfilled

执行 onFulfilled 函数

最终失败状态 -> rejected

执行 reject 函数,状态改变为 rejected

执行 onRejected 函数

then 方法

then 方法为 Promise 对象注册了 onFulfilled 和 onRejected 函数

catch 方法

catch 方法为Promise 对象注册了 onRejected 函数

1. Promise.resolve

   静态方法 Promise.resolve(value)可以认为是 new Promise方法的语法糖,比如Promise.resolve(42) 可以认为是以下代码的语法糖。

new Promise(function (resolve) {
    resolve(42)
})

这个静态方法会让Promise对象立即进入确定(即resolved) 状态,并将42传递给后面 then 里所指定的 onFulfilled函数。作为 new Promise的快捷方式,在进行 Promise 对象的初始化或者编写测试代码的时候都非常方便。

简单总结一下 Promise.resolve方法的话,它的作用就是将传递给它的参数填充 Fulfilled 到 Promise 对象后并返回这个 Promise 对象。

2. Promise.reject

 Promise.reject(error)是和Promise.resolve(value)类似的静态方法,是 new Promise 方法的快捷方式。比如 Promise.reject(new Error("Promise reject error")) 就是下面代码的语法糖形式

new Promise(function (reject) {
    reject(new Error("Promise reject error"))
})

     简单总结一下 Promise.reject方法的话:它的功能是调用该 Promise对象通过then指定的 onRejected函数,并讲错误(Error)对象传递给这个onRejected函数

3. Promise.then

   Promise.then(onFulfilled, onRejected)

① 函调函数异步执行

var promise = new Promise((resolve, reject) => {
  console.log("inner Promise"); // 1
  setTimeout(() => {
    resolve("Fashion Barry"); // 3
  }, 1000);
});
promise.then((res) => {
  console.log("res", res);
});
console.log("outer promise"); // 2
// Promise 实际是一个同步函数,then 方法才是异步
// 所以输出顺序如上

Promise/A+规范统一规定:Promise 只能使用异步调用方式

② 返回值  

   不管你在回调函数 onFulfilled中会返回一个什么样的值,或者不返回值,该值都会由 Promis.resolve(return 的返回值) 进行响应的包装处理。因此,最终 then的结果都是返回一个新创建的 Promise对象。


   也就是说,Promis.then不仅仅是注册一个回调函数那么简单,它还会将回调函数的返回值进行变换,创建并返回一个Promise 对象。正是 then函数中有了这样返回值的机制,才能使得在整个Promise链式结构当中,每个then方法都能给 下一个then方法传递参数。现在我们知道怎么返回的Promise是之前的还是新的?另外该Promise的状态又是如何?

var aPromise = new Promise((resolve, reject) => {
  resolve("aPromise");
});
var thenPromise = aPromise.then((res) => {
  console.log("thenPromise: ", res);
});
var catchPromise = aPromise.catch((err) => {
  console.error("catchPromise: ", err);
});
console.log(aPromise !== thenPromise); // true
console.log(thenPromise !== catchPromise); // true
console.log(aPromise, thenPromise, catchPromise); // Promise { "aPromise" }, Promise { <pending> }, Promise { <pending> }

从上面结果来看,实际上不管是调用 then还是catch方法, 都返回了一个新的Promise对象

③ promise穿透

  我们先来举个例子:

Promise.resolve("Barry")
.then(Promise.resolve("Barry Promise"))
.then((result) => {
  console.log("result", result); // "Barry"
});

如果你认为输出的是【 Barry Promise 】,那么你就错了,实际上他输出的是 【 Barry 】


产生这么的输出是因为你给then方法传递了一个非函数(比如promise对象)的值,代码会这样理解 : then(null),因此导致了前一个promise的结果产生了坠落的效果,也就是和下面代码是一样的, 代码直接穿透了then(null)进入了下一层链:

Promise.resolve("Barry")
.then(null)
.then((result) => {
  console.log("result", result); // "Barry"
});

随意添加多个then(null)结果都是一样的

Promise.resolve("Barry")
.then(null)
.then({ name: "My name is Barry" })
.then(null)
.then((result) => {
  console.log("result", result); // "Barry"
});

4. Promise.catch

① 语法糖的本质

       这里我们再说一遍,实际上Promise.catch只是promise.then(undefined, onRejected) 方法的一个别名而已。也就是说,这个方法用来注册当Promise对象状态变为 Rejected时 的回调函数。可以看下面代码,两者写法是等价的,但是很明

// 第一种写法
Promise.resolve()
  .then((data) => console.log(data))
  .then(undefined, (err) => console.log(err));
// 第二种写法
Promise.resolve()
  .then((data) => console.log(data))
  .catch((err) => console.log(err));

那么我们现在来说说为什么推荐使用第二种方法,而不是第一种:


使用promise.then(onFulfilled, onRejected) 的话,在onFulfilled中发生异常的话,onRejected 中是捕获不到这个异常的。而且如果链式很长,每一条链上都要这么写。

在promise.then(onFulfilled).catch(onRejected) 的情况下.then中产生异常能在.catch 中捕获。.then和.catch本质上是没有区别的, 需要分场合使用

② 只有一个主人

       我们上面已经说过了,在书写很长的Promise链式,从代码清晰度和简易程度来讲,在最后添加 catch是远远在每一层链上写onRejected回调函数是要好的,因为catch可以捕获 Promise链中每一层节点的错误,这句话本身没有错,但从这句话延伸出一种错误的理解:catch 同时监控着所有节点。实际上catch函数在同一个时间点只属于某一个Promise,因为它的主人是随着程序 的执行而不断变化的,我们来举个例子:

let p1 = new Promise((resolve, reject) => {
  // 第一层执行逻辑
  resolve("first promise"); // Promise(1)
})
  .then((res) => {
    // 第二层执行逻辑
    return "second promise"; // Promise(2)
  })
  .then((res) => {
    // 第三层执行逻辑
    return "third promise"; // Promise(3)
  })
  .catch((err) => {
    console.log("err", err);
  });

在上述例子中,如果整个程序每一步都正确执行,那么会顺序产生三个Promise对象,分别是 Promise(1),Promise(2),Promise(3):


可是如果在第一层具体执行逻辑出错了后,那实际上后面的两个then 中的回调函数压根不会被异步执行,所以会直接异步触发catch中的回调函数执行, 所以这种情况下catch是Promise(1)对象的catch。

如果第一层具体执行逻辑正确执行,就会异步触发第二个then中的回调函数执行,那么同理 ,在第二次具体执行逻辑抛出错误,会导致Promise(2)的状态变化,所以这种情况下catch 是Promise(2)对象的catch。

同理Promise(3)也是如此

总结下来就是:整个Promise链中,catch只属于异步触发它当中回调函数 执行的那个Promise,并不属于所有 Promise

5. Promise.finally

   promise.finally方法的回调函数不接受任何参数,这意味着finally没有办法 知道,前面的Promise状态到底是fulfilled还是rejected 。这表明,finally方法里面的操作,应该是与Promise状态无关的,不依赖于 Promise的执行结果。我们来看下面代码:

var p1 = new Promise((resolve, rejevt) => {
  setTimeout(() => {
    resolve;
  }, 1000);
});
p1.then((res) => console.log(res))
  .catch((err) => console.log(err))
  .finally(() => console.log("finally"));

finally本质上是then方法的特例。我们来看下面伪代码:

promise.finally(() => {
  // 执行逻辑
});
// 上面代码等同于下面
promise.then(
  (onFulilled) => {
    // 语句
    return onFulilled;
  },
  (onRejected) => {
    // 语句
    throw onRejected;
  }
);

上面代码中,如果不使用finally方法,同样的语句需要为成功和失败的状态各写一次。 有了finally方法,则只需要写一次。那么它是如何实现的呢?

Promise.prototype.finally = function (callback) {
  let p = this.constructor;
  return this.then(
    (value) => p.resolve(callback()).then(() => value),
    (reason) =>
      p.reject(callback()).then(() => {
        throw reason;
      })
  );
};
var p = new Promise((resoleve, reject) => {
  setTimeout(() => {
    reject("Promise err");
  }, 1000);
});
p.catch((err) => console.log("err", err)).finally(() => {
  console.log("finally");
});

上述代码中,不管前面的Promisefulfilled还是rejected ,都会执行回调函数callback

6. Promise.all

   Promise.all接受一个promise对象的数组作为参数,当这个数组里的所有 Promise 对象 全部变为resolve或者reject状态的时候,它才会去调用.then方法。

   传递给Promise.allpromise并不是一个个的顺序执行的,而是同时开始、并行执行的,我们可以看下面例子

var promise1 = new Promise((resoleve, reject) => {
  setTimeout(() => {
    resoleve("promise1--3000");
  }, 3000);
});
var promise2 = new Promise((resoleve, reject) => {
  setTimeout(() => {
    resoleve("promise2--1000");
  }, 1000);
});
var promise3 = new Promise((resoleve, reject) => {
  setTimeout(() => {
    resoleve("promise3--5000");
  }, 5000);
});
var promiseArr = [promise1, promise2, promise3];
console.time("promiseArr");
Promise.all(promiseArr)
  .then((res) => {
    console.log("res", res); // ['promise1--3000', 'promise1--1000', 'promise1--5000']
    console.timeEnd("promiseArr"); // 5523.29296875 ms
  })
  .catch((err) => console.log(err));


为什么这个例子可以看出来Promise.all()是并行的呢?因为所有Promise执行完只用了5秒,如果3个 Promise是按照顺序执行的,那么应该是9秒或者,在5-9之间,因为4个Promise并不是同时执行的,同时执行的 话总时间就是那个花费时间最长的Promise

Promise.all()重要细节点 (面试常考):


如果所有的Promise中只有一个执行错误,那么整个Promise.all不会走Promise.all().then() 而是走Promise.all().catch()这个流程了。但是要注意的是虽然走到了Promise.all().catch()这个流程 ,但是其他 Promise 还是会正常执行,但不会返回结果

要注意Promise.all()的返回值顺序,Promise.all().then()的返回值顺序和传入的顺序是一致的,笔试时 遇到手写Promise.all()时要注意。

7. Promise.allSettled

Promise.allSettled()的入参和Promise.all、Promise.race一样,接受一个promise 对象的数组作为参数,也是同时开始、并行执行的。但是Promise.allSettled的返回值需要注意以下几点:


Promise.allSettled不会走进catch,当所有输入Promise都被履行或者拒绝时, statusesPromise 会解析一个具有具体完成状态的数组


{ status: 'fulfilled', value:value } :如果相应的promise被履行

{ status: 'rejected', reason: reason }:如果相应的promise被拒绝我们看下面示例:

var promise1 = new Promise((resoleve, reject) => {
  setTimeout(() => {
    reject(new Error("promise1--3000"));
    // resoleve("promise1--3000");
  }, 3000);
});
var promise2 = new Promise((resoleve, reject) => {
  setTimeout(() => {
    // reject(new Error("promise1--1000"))
    resoleve("promise2--1000");
  }, 1000);
});
var promise3 = new Promise((resoleve, reject) => {
  setTimeout(() => {
    resoleve("promise3--5000");
    // reject(new Error("promise1--5000"))
  }, 5000);
});
var promiseArr = [promise1, promise2, promise3];
console.time("promiseArr");
Promise.allSettled(promiseArr)
  .then((res) => {
    console.log("res", res);
    console.timeEnd("promiseArr");
  })
  .catch((err) => console.error(err))
  .finally(() => console.log("finally"));

总结一下:Promise.allSettled()在你需要执行平行和独立的异步操作并收集所有结果时非常有效, 即使某些异步操作可能失败。

8. Promise.race

 Promise.rece()的使用方法和 Promise.all一样,接收一个promise 对象的数组为参数,Promise.race是要有一个promise对象进入Fulfilled或者 Rejected状态的话,就会继续进行后面的处理。这里依旧有两个点要注意:


和Promise.all一样是所有数组当中的Promise同时并行的

Promise.race 在第一个Promise对象变为Fulfilled之后,并不会 取消其他promise对象的执行。

Promise.race接受的是一个Promise对象数组,但是返回的确实最先完成Fulfilled 或者最先被Rejected的一个Promise的结果

下面我们来举个例子:

let arr = [1000, 3000, 5000, 7000];
let promiseArr = [];
for (let i = 0; i < arr.length; i++) {
  let newPromise = new Promise((resolve, reject) => {
    if (i === 0) {
      reject(new Error("第二个错误"));
    } else {
      setTimeout(() => {
        console.log(arr[i]);
        resolve(arr[i]);
      }, arr[i]);
    }
  });
  promiseArr.push(newPromise);
}
Promise.race(promiseArr)
  .then((res) => {
    console.log(res);
  })
  .catch((err) => {
    console.log(err);
  });
// 控制台报错
// 3000
// 5000
// 7000

这里我们再复习一下Node当中事件循环的知识:


第一层循环:i为0时,异步触发了Promise.race().catch(),这里面的回调代码被放在了微任务队列中, 后面的3个setTimeout宏任务的回调函数代码被放进了timer阶段中的队列当中(其实并不是这样,因为 三个定时器都有延迟,都是在后面的事件循环中添加进来的)

第二层循环:清空微任务对列,所以控制台打印出了错误,然后清空宏任务,分别打印出3000/5000/7000

9. Promise.any

 Promise.any的入参和Promise.all、Promise.race、Promise.allSettled一样, 接收一个promise对象的数组作为参数。


只要其中有一个Promise成功执行,就会返回已经成功执行的Promise的结果

如果这个promise对象的数组中没有一个promise 可以成功执行(即所有的 promise都失败 ),就返回一个失败的promise 和AggregateError类型的实例,它是Error的一个子类,用于把单一的错误集合 在一起

var promise1 = new Promise((resoleve, reject) => {
  setTimeout(() => {
    // reject(new Error("promise1--3000"));
    resoleve("promise1--3000");
  }, 3000);
});
var promise2 = new Promise((resoleve, reject) => {
  setTimeout(() => {
    // reject(new Error("promise2--1000"))
    resoleve("promise1--1000");
  }, 1000);
});
var promise3 = new Promise((resoleve, reject) => {
  setTimeout(() => {
    // resoleve("promise3--5000");
    reject(new Error("promise1--5000"))
  }, 5000);
});
var promiseArr = [promise1, promise2, promise3];
console.time("promiseArr");
Promise.any(promiseArr)
  .then((res) => {
    console.log("res", res); // res promise1--1000
    console.timeEnd("promiseArr");
  })
  .catch((err) => console.error(err)); 
  //所有的Promise都失败, AggregateError: All promises were rejected

总计一下Promisea.any的应用场景:如果我们现在有多台服务器,则尽量使用响应速度最快的服务器,在这种情况下, 可以使用Promise.any()方法从最快的服务器接收响应。


相关文章
|
7月前
|
前端开发
【面试题】吃透Promise?先实现一个再说(包含所有方法)(二)
【面试题】吃透Promise?先实现一个再说(包含所有方法)(二)
|
7月前
|
存储 运维 前端开发
【面试题】吃透Promise?先实现一个再说(包含所有方法)(一)
【面试题】吃透Promise?先实现一个再说(包含所有方法)(一)
|
1月前
|
前端开发 索引
Promise.all() 方法的参数可以是什么类型?
综上所述,`Promise.all()` 方法的参数类型较为灵活,但无论使用哪种类型的可迭代对象作为参数,其核心的异步操作处理逻辑和成功失败的判断机制都是一致的,都是为了方便地处理多个异步操作的并发执行和结果汇总。
|
1月前
|
存储 前端开发
除了 Promise.all(),还有哪些方法可以处理异步并发操作?
在上述示例中,`concurrentPromises` 函数接受一个Promise数组和最大并发数作为参数,通过手动控制并发执行的Promise数量,实现了对异步操作的并发控制,并在所有Promise完成后返回结果数组。
|
21天前
|
前端开发
Promise.allSettled()方法和Promise.race()方法有什么区别?
`Promise.allSettled()` 提供了一种更全面、更详细的方式来处理多个 `Promise`,而 `Promise.race()` 则更强调速度和竞争。我们需要根据具体的需求来选择使用哪种方法。
|
26天前
|
前端开发
`Promise.all()`方法在处理数组形式参数时的执行机制
Promise.all()` 提供了一种方便的方式来同时处理多个异步操作,并在它们都完成后获取到所有的结果,使得我们能够更高效地进行异步任务的组合和处理。
|
26天前
|
前端开发
`Promise.allSettled()`方法与`Promise.all()`方法有何不同?
`Promise.allSettled()` 提供了一种更灵活和全面的方式来处理多个 `Promise`,使得我们能够更好地应对各种异步操作的情况,尤其是需要详细了解每个 `Promise` 结果的场景。
|
21天前
|
监控 调度
在什么情况下应该使用 Promise.allSettled() 方法?
总的来说,`Promise.allSettled()` 为我们处理多个异步任务提供了一种更灵活、更全面的方式,使我们能够更好地应对各种复杂的情况,确保在获取到所有任务结果的同时,能够进行更有效的后续处理和决策。
|
26天前
|
前端开发 索引
Promise.all() 方法的参数可以是哪些数据类型?
`Promise.all()` 方法的参数具有很大的灵活性,可以适应多种不同的场景和需求,方便地处理多个异步操作的并发执行和结果汇总。
|
1月前
|
前端开发
Promise.race() 方法在什么场景下使用?
`Promise.race()` 方法通过其独特的竞争机制,在需要快速获取结果、设置超时控制、实现快速失败以及根据条件动态选择异步操作等场景中,能够提供简洁有效的解决方案,帮助优化异步操作的执行流程和提高系统的响应性能。