Promise用法的简单学习记录

主要学习和摘录《JavaScript重难点实例精讲》中的内容

1、Promise产生的原因

多个异步请求,想要顺序执行,就产生了回调地狱的写法:

      setTimeout(() => {
        console.log("1");
        setTimeout(() => {
          console.log("2");
          setTimeout(() => {
            console.log("3");
          }, 1000);
        }, 1000);
      }, 1000);
      console.log("A");
      //执行结果是:A、1、2、3

“回调地狱”存在以下几个问题:

  • 代码臃肿,可读性差。
  • 代码耦合度高,可维护性差,难以复用。
  • 回调函数都是匿名函数,不方便调试。

Promise为异步编程提供了一种更合理、更强大的解决方案。

2、Promise的生命周期

Promise对象有三种状态:

  • pending,进行中
  • fulfilled,成功
  • rejected,失败

Promise对象创建后,状态为pending,执行后,要么成功,要么失败,状态只改变一次。

3、基本用法

3.1 创建Promise对象

语法:

const promise = new Promise((resolve, reject) => {
    // 异步请求处理
    if(/ 异步请求标识 /) {
        resolve();
    } else {
        reject();
    }
});

示例:

      const p1 = new Promise((resolve) => {
        setTimeout(() => {
          console.log("1");
          resolve(true);
        }, 1000);
      });
      console.log("A");
      //输出:A、1

注意:

  • Promise对象一旦创建,就会立马执行。
  • 执行时等待执行resolve或reject来确定Promise的最终状态是成功还是失败。
  • resolve()函数和reject()函数可以传递参数,作为后续.then()函数或者.catch()函数执行时的数据源。

3.2 执行顺序的判断

      const p1 = new Promise((resolve) => {
        console.log("1");
        resolve("2");
      });
      p1.then((res) => {
        console.log(res);
      });
      console.log("A");

执行结果是:1、A、2。
分析:Promise对象创建后立马输出1、然后执行then前会先把当前线程中的所有同步代码执行完,所以输出A,最后执行then输出2.

更多判断方法,参考事件循环-宏任务和微任务

3.3 then函数

then函数是Promise状态改变后要执行的回调函数。

  • 第一个参数是Promise状态变为成功后,要执行的回调函数(参数是resolve传递的参数)。
  • 第二个参数是Promise状态变为失败后,要执行的回调函数(调用了reject或抛出了异常;参数是reject传递的参数)
  • 可以链式调用,then的返回值会传给后面的then。
      const p1 = new Promise((resolve) => {
        resolve(1);
      });
      p1.then((res) => {
        console.log(res);
        return 2;
      })
      .then((res) => {
        console.log(res);
        return 3;
      })
      .then((res) => {
        console.log(res);
      });
      console.log("A");

3.4 catch函数

一般不推荐在then的第二个回调函数中处理reject的情况,而是通过catch来捕捉问题。(一方面是then的第二个回调不能捕捉第一个回调里面的异常,见下文场景2中的例子;另一方面不然每次then都需要提供第二个回调来处理异常)

      const p1 = new Promise((resolve) => {
        resolve(1);
      });
      p1.then((res) => {
        console.log(res);
        return 2;
      })
      .then((res) => {
        throw new Error("test err");
        console.log(res);
        return 3;
      })
      .then((res) => {
        console.log(res);
      })
      .catch((err) => {
        console.log(err);
      });
      console.log("A");

输出:A、1、Error: test err。
执行第二个then的时候抛出了异常,后面就都不会执行了。

事实上只要在Promise执行过程中出现了异常,就会被自动抛出,并触发reject(err),而不用我们去使用try…catch,在catch()函数中手动调用reject()函数。
需要注意的是,如果一个Promise的状态已经变成fulfilled成功状态,再去抛出异常,是无法触发catch()函数的。这是因为Promise的状态一旦改变,就会永久保持该状态,不会再次改变。

3.5 Promise静态函数

3.5.1 Promise.all()

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

用于将多个Promise实例包装成一个新的Promise实例,返回的新Promise实例p的状态由3个Promise实例p1、p2、p3共同决定,总共会出现以下两种情况。

  • 只有p1、p2、p3全部的状态都变为fulfilled成功状态,p的状态才会变为fulfilled状态,此时p1、p2、p3的返回值组成一个数组,作为p的then()函数的回调函数的参数。
  • 只要p1、p2、p3中有任意一个状态变为rejected失败状态,p的状态就变为rejected状态,此时第一个被reject的实例的返回值会作为p的catch()函数的回调函数的参数。
  • 需要注意的是,作为参数的Promise实例p1、p2、p3,如果已经定义了catch()函数,那么当其中一个Promise状态变为rejected时,并不会触发Promise.all()函数的catch()函数。
  • 如果想要Promise.all()函数能触发catch()函数,那么就不要在p1、p2实例中定义catch()函数。

3.5.2 Promise.race()

表示的是如果多个Promise实例中有任何一个实例的状态发生改变,那么这个新实例的状态就随之改变,而最先改变的那个Promise实例的返回值将作为新实例的回调函数的参数。

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

使用Promise.race()函数可以实现这样一个场景:假如发送一个Ajax请求,在3秒后还没有收到请求成功的响应时,会自动处理成请求失败。

const p1 = ajaxGetPromise('/testUrl');
const p2 = new Promise(function (resolve, reject) {
      setTimeout(() => reject(new Error('request timeout')), 5000)
});
const p = Promise.race([p1, p2]);
p.then(console.log).catch(console.error);

3.5.3 Promise.resolve()

用于将传入的变量转换为Promise对象,它等价于在Promise函数体内调用resolve()函数。

Promise.resolve('hello');
// 等价于
new Promise(resolve => resolve('hello'));

3.5.4 Promise.reject()

函数在执行后Promise的状态会立即变为rejected,从而会立即进入catch()函数中做处理,等价于在Promise函数体内调用reject()函数。

const p = Promise.reject('出错了');
// 等价于
const p = new Promise((resolve, reject) => reject('出错了'));

4、用法实例

场景1:Promise代码与同步代码在一起执行

const promise = new Promise((resolve, reject) => {
    console.log(1);
    resolve();
    console.log(2);
});
promise.then(() => {
    console.log(3);
});
console.log(4);

执行结果:1、2、4、3

  • Promise在创建后会立即执行,所有同步代码按照书写的顺序从上往下执行,包括Promise外的同步代码,因此会先输出“1 2 4”​。
  • resolve()函数或者reject()函数会在同步代码执行完毕后再去执行。
  • 当resolve()函数或者reject()函数执行后,进入then()函数或者catch()函数中执行,实例中调用了resolve()函数,会进行到then()函数中,因此会再输出“3”​。

更多判断方法,参考事件循环-宏任务和微任务

场景2:同一个Promise实例内,resolve()函数和reject()函数先后执行

const promise2 = new Promise((resolve, reject) => {
  resolve("success1");
  reject("error");
  resolve("success2");
  console.log('start');
});

promise2
  .then((res) => {
    console.log("then: ", res);
  })
  .catch((err) => {
    console.log("catch: ", err);
  });

执行结果:

start
then:  success1

一个Promise的实例只能有一次状态的变更,当执行了resolve()函数后,后续其他的reject()函数和resolve()函数都不会执行,然后Promise进入then()函数中做处理。

场景3:同一个Promise实例自身重复执行

const promise3 = new Promise((resolve, reject) => {
    setTimeout(() => {
        console.log('once');
        resolve('success');
    }, 1000);
});
const start = Date.now();
promise3.then((res) => {
    console.log(res, Date.now() - start);
});
promise3.then((res) => {
    console.log(res, Date.now() - start);
});

执行结果:

once
success 1015
success 1016

同一个Promise的实例只能有一次状态变换的过程,在状态变换完成后,如果成功会触发所有的then()函数,如果失败会触发所有的catch()函数。
具体的秒数差取决于运行的环境,很可能会相差几毫秒。

场景4:在then()函数中返回一个异常

Promise.resolve()
  .then(() => {
    console.log(1);
    return new Error("error!!!");
  })
  .then((res) => {
    console.log(2);
    console.log("then: ", res);
  })
  .catch((err) => {
    console.log(3);
    console.log("catch: ", err);
  });

执行结果:

1
2
then:  Error: error!!!

在then()函数中用return关键字返回了一个“Error”​,依然会按照正常的流程走下去,进入第二个then()函数,并将Error实例作为参数传递,不会执行后续的catch()函数。
这个不同于使用throw抛出一个Error,如果是throw抛出一个Error则会被catch()函数捕获。

场景5:then()函数接收的参数不是一个函数

Promise.resolve(1)
.then(2)
.then(Promise.resolve(3))
.then(console.log);//1

在Promise的then()函数或者catch()函数中,接收的是一个函数,函数的参数是resolve()函数或者reject()函数的返回值。
而如果传入的值是非函数,那么就会产生值穿透现象:传递的值会被直接忽略掉,继续执行链式调用后续的函数。

场景6:两种方法处理rejected状态的Promise

Promise.resolve()
  .then(
    function success(res) {
      throw new Error("error");
    },
    function fail1(e) {
      console.error("fail1: ", e);
    }
  )
  .catch(function fail2(e) {
    console.error("fail2: ", e);
  });

执行结果:fail2: Error: error

虽然这两种方法都能处理Promise状态变为rejected时的回调,但是then()函数的第二个函数却不能捕获第一个函数中抛出的异常,而catch()函数却能捕获到第一个函数中抛出的异常。
这也是我们推荐使用catch()函数去处理Promise状态异常回调的原因。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值