五分钟搞定 Promise,就看这篇文章

215 阅读9分钟

说到 Promise, 其实很多人用的都稀里糊涂,我们常常被它不同的写法搞得头皮发麻,比如: Promise.resolve(value)new Promise() 到底怎么用?通过这篇文章,你会对 Promise 有新的认识!

说明:文章中所有的代码直接可以在开发者工具里面跑,不用重启任何服务。主打一个轻松掌握。

Promise 的特性:

1.状态不可变

2.链式调用

3.异步处理(微任务)

4.独特的错误捕获方式,只能用catch(),不能用 try catch

一. Promise 状态

首先 Promise 的三个状态是定死的,没有任何入口给开发者修改。

**初始状态** -> pending,在resolve 或者 reject 调用之前都处于这个状态。

**最终成功状态** -> fulfilled,执行 resolve 函数,状态改变为 fulfilled,执行 onFulfilled 函数。

**最终失败状态** -> rejected,执行 reject 函数,状态改变为 rejected,执行 onRejected 函数。

其次,一旦Promise的状态从pending变为fulfilled或rejected,就不会再改变。

最后,Promise构造函数接受一个函数作为参数,这个函数有两个参数:resolve和reject。resolve函数用于将 Promise 的状态从pending 变为 fulfilled ,并传递一个结果值;reject函数则用于将 Promise 的状态从pending 变为 rejected,并传递一个错误原因。

你是不是想看看它的状态?我们就看看呗

打开F12直接在console里面写代码

image.png

console.log(new Promise(()=>{}))
console.log(new Promise((reject)=>{reject('你是个啥?')}))
console.log(new Promise((resolve)=>{resolve('成功了!')}))
console.log(new Promise((resolve,reject)=>{reject('失败了')}))

想下第二个是咋回事?评论区说说看。

二. 方法

1.resolve()

异步执行

在开发中我们常见有人这样搞 return Promise.resolve('结果');它等于

return Promise.resolve('结果')

//等同于

return new Promise(function (resolve) {
    resolve('结果')
})

一般我们会将Promise.resolve()认为是 new Promise((resolve)=>{resolve('成功了!')})的语法糖,说人话就是简写版。

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

2.reject()

异步执行

如上面同理:Promise.reject()就是 new Promise((resolve, reject)=>{reject('失败了!')}) 的语法糖。

return Promise.reject('失败了')

//等同于

return new Promise(function (resolve,reject) {
    reject('失败了')
})

这个静态方法会让Promise对象立即进入 rejected 状态,并将结果传递给后面 catch 里所指定的 onRejected 函数。

3.then()

异步执行

Promise.then(onFulfilled, onRejected)

then的写法有很多,只要返回出来是Promise就可以点then

Promise.then(onFulfilled, onRejected)

new Promise().then(()=>{})

Promise.resolve().then(()=>{})

最终 then的结果都是返回一个新创建的 Promise对象。 也就是说,Promis.then不仅仅注册一个回调函数那么简单,它还会将回调函数的返回值进行变换,创建并返回一个Promise 对象。正是 then函数中有了这样返回值的机制,才能使得在整个Promise链式结构当中,每个then方法都能给 下一个then方法传递参数。现在我们知道怎么返回的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对象。

值得注意的是then会出现promise的穿刺,就是本来then()里面应该传入的参数是函数,但是当你传入null或者对象的时候,它就会出现塌陷,直接跳过,比如下面的案例:

Promise.resolve("Barry")
.then(Promise.resolve("Barry Promise")) // 这个对象会导致塌陷,打印结果是Barry
.then((result) => {
  console.log("result", result); // "Barry"
});

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

总之then的穿刺是因为他的传参不正确导致的。

4. catch()

实际上Promise.catch只是promise.then(undefined, onRejected) 方法的一个别名而已,也就是说,它其实就是执行的是thenonRejected函数。

// 第一种写法
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));

在链式调用的时候,不管前面有多少个then,但是catch只有一个,如果中间其中一个then报错了,它后面的then都不再执行,直接进入catch。总结为一点就是:谁报错,catch就是谁的。

5.finally

Promise.finally()方法的回调函数不接受任何参数,这意味着finally没有办法知道,前面的Promise状态到底是fulfilled还是rejected 。说明:finally方法里面的函数,与Promise状态无关的,不管上面的Promise执行成功了还是失败了,finally()里面的函数都会执行一遍。

var p1 = new Promise((resolve, rejevt) => {
  setTimeout(() => {
    resolve;
  }, 1000);
});
 
p1.then((res) => console.log(res))
  .catch((err) => console.log(err))
  .finally(() => console.log("finally"));
6.all
  1. Promise.all()入参都是一个 Promise 的数组,数组中所有的 Promise 并行执行。
  2. Promise 数组中不管谁先执行完,都不影响其他 Promise 的执行。
  3. 如果其中一个 Promise 报错了,他会直接执行catch。其中一个 Promise 执行失败,进入 catch ,但他并不影响数组中其他Promise的执行。
  4. 如果所有的 Promise 都成功了,才进入then,将所有成功的结果装在数组里面返回出来。简言之就是它的入参是个数组,他的出参也是一个数组。
  5. Promise.all()入参的顺序,和then()里面返回值的顺序是一致的。

image.png

来个报错的

image.png

多个报错,只捕获第一个

image.png

 
var promise1 = new Promise((resolve, reject) => {
    setTimeout(() => {
        console.log('100秒后')
      resolve("第一个");
    }, 100);
  });
  var promise2 = new Promise((resolve, reject) => {
    setTimeout(() => {
       // resolve("第二个");
       console.log('300秒后')
        reject("第二个错了");
    }, 300);
  });
  var promise3 = new Promise((resolve, reject) => {
    setTimeout(() => {
        console.log('500秒后')
      resolve("第三个");
    }, 500);
  });
   
  var promiseArr = [promise1, promise2, promise3];
  console.time("promiseArr");
  Promise.all(promiseArr)
    .then((res) => {
      console.log("res", res); 
      console.timeEnd("promiseArr"); 
    })
    .catch((err) => console.log('最终的结果是'+ err));

从这个例子你可以看出, Promise.all()是并行的。因为所有Promise执行完只用了5秒,如果3个 Promise是按照顺序执行的,那么应该是9秒。

7. Promise.allSettled
  1. Promise.allSettled()入参也是一个 Promise 的数组,数组中所有的 Promise 并行执行。
  2. Promise.allSettled()只收集结果,不管对错。只执行then和finally,不执行catch。
  3. Promise 数组中不管谁先执行完,都不影响其他 Promise 的执行。
  4. Promise.all()入参的顺序,和then()里面返回值的顺序是一致的。
  5. 当所有输入Promise都被履行或者拒绝时, statusesPromise 会解析一个具有具体完成状态的数组
{ status: 'fulfilled', value:value } :如果相应的promise被履行
{ status: 'rejected', reason: reason }:如果相应的promise被拒绝

也就是说不管数组里面的Promise是成功了还是失败了,我的任务就是把这些结果全部打包给你,怎么处理就是你的事情了。

代码如下:

image.png

 
var promise1 = new Promise((resolve, reject) => {
    setTimeout(() => {
        console.log('100秒后')
      resolve("第一个");
    }, 100);
  });
  var promise2 = new Promise((resolve, reject) => {
    setTimeout(() => {
       // resolve("第二个");
       console.log('300秒后')
        reject("第二个错了");
    }, 300);
  });
  var promise3 = new Promise((resolve, reject) => {
    setTimeout(() => {
        console.log('500秒后')
      resolve("第三个");
    }, 500);
  });
   
  var promiseArr = [promise1, promise2, promise3];
  console.time("promiseArr");
  Promise.allSettled(promiseArr)
    .then((res) => {
      console.log("res", res); 
      console.timeEnd("promiseArr"); 
    })
    .catch((err) => console.log('最终的结果是'+ err))
    .finally(()=>{console.log("我是个大美女")})

Promise.allSettled()就相当于一个结果收集器。

8.Promise.race

异步执行

  1. Promise.race() Promise.all()入参都是一个 Promise 的数组,数组中所有的 Promise 并行执行。
  2. Promise 数组中不管谁先执行完,都不影响其他 Promise的执行。
  3. Promise.race() 入参的数组中,谁先第一个完成 Fulfilled 或者 Rejected,我就返回谁。

执行案例:

image.png

  var promise1 = new Promise((resolve, reject) => {
    setTimeout(() => {
        //resolve("成功1");
        reject(new Error("错了"))
    }, 100);
  });
  var promise2 = new Promise((resolve, reject) => {
    setTimeout(() => {
        console.log(`上面错了, 和我有关系吗?我是第 2 个 Promise`)
        resolve("成功2");
    }, 300);
  });
  var promise3 = new Promise((resolve, reject) => {
    setTimeout(() => {
        console.log(`上面错了, 和我有关系吗?我是第 3 个 Promise`)
        reject(new Error("错了"))
    }, 500);
  });

  var promiseArr = [promise1, promise2, promise3];

  Promise.race(promiseArr)
    .then((res) => console.log("最终的结果是", res))
    .catch((err) => console.error("最终的结果是", err)); 
9.Promise.any

异步执行

  1. Promise.any() Promise.all、Promise.race、Promise.allSettled一样,入参都是一个 Promise 的数组,数组中所有的 Promise 并行执行。
  2. Promise 数组中不管谁先执行完,都不影响其他 Promise的执行。
  3. Promise 入参的数组中,谁先第一个完成 Fulfilled,我就返回谁。

执行案例:

image.png

var promise1 = new Promise((resolve, reject) => {
    setTimeout(() => {
        //resolve("成功1");
        reject(new Error("错了"))
    }, 100);
  });
  var promise2 = new Promise((resolve, reject) => {
    setTimeout(() => {
        console.log(`上面错了, 和我有关系吗?我是第 2 个 Promise`)
        resolve("成功2");
    }, 300);
  });
  var promise3 = new Promise((resolve, reject) => {
    setTimeout(() => {
        console.log(`上面错了, 和我有关系吗?我是第 3 个 Promise`)
        reject(new Error("错了"))
    }, 500);
  });
  var promiseArr = [promise1, promise2, promise3];

  Promise.any(promiseArr)
    .then((res) => {
      console.log("最终的结果是", res);
    })
    .catch((err) => console.error(err)); 
总结

Promise.all() 和 Promise.allSettled() 的区别是: all会管对错,但是 allSettled 不会管对错。 Promise.race() 和 Promise.any() 之间的区别是:race不管对错,谁先拿到值,我就返回谁。any是谁先拿到正确的值,我就返回谁。

三. 微任务

我们都知道promise里面的代码都会放到微任务里面去,但是你需要知道的是并不是所有的代码都是异步执行的。

  1. new Promise() 构造函数里面的代码是同步执行的
  2. 只有resolve(),reject(),then(),catch(), finally(), all(), allSettled(), race(),any() 等里面的代码才是异步执行的。
  3. 在时间循环机制里面,任务分为宏任务和延迟任务,还有微任务三种,优先进入的是宏任务,所有的宏任务执行完毕以后,再执行延迟任务。
  4. 每个宏任务里面又维护着一个微任务队列,微任务的触发时机是上一个宏任务执行完毕,下一个宏任务执行前。说人话,就是两个宏任务的切换空隙中间执行。当所有的宏任务都执行完毕以后才去执行延迟任务。 5.执行过程中, 宏任务产生的宏任务直接追加到本次宏任务的队列的后面去,宏任务产生的延迟任务也会追加到本次延迟任务队列的后面去,宏任务产生的微任务直接追加到当前宏任务内部维护的微任务队列的后面去。

四. 错误捕获

Promise的错误捕获必须是catch,try catch 不行

image.png

代码如下:

 try{
        new Promise((resolve, reject) => {
            setTimeout(() => {
                //resolve("成功1");
                reject(new Error("错了"))
            }, 100);
        }).then(()=>{
            console.log('成功了')
        }).catch((error)=>{
            console.log(`错误结果:`, error) 
        })
    }catch(error){
        console.log("捕获到了吗?")
    }

当你看到我的这篇文章的时候,直接F12打开控制台,就能测试了,你看我就是这样干的

image.png