【前端基础】简单易懂的Promise

ES6之前,以往的异步方法,是使用setTimeout嵌套处理代码,如何再嵌套setTimeout。

// setTimeout异步方式
setTimeout(() => {
  const f1 = () => {
    //...逻辑代码
    return 1;
  };
  let res1 = f1();
  setTimeout(() => {
    const f2 = () => {
      return res1 + 1;
    };
    let res2 = f2();
    setTimeout(() => {
      //使用res2值做操作。
      // 。。。
    }, 3000);
  }, 3000);
}, 3000);

以上做了三层嵌套,会发现理解起来和维护会比较困难,所以ES6之后就增加了Promise规范和浏览器promiseAPI。

1、Promise基础

先介绍一个小知识点:

ECMASript变量值可以分为两种类型数据:原始值和引用值。

  • 原始值:最简单的数据,对应的类型是JS的原始类型:Undefined、Null、Boolean、Number、String、Symbol。
    例如:
const c = 0; //0为原始值, c的类型为原始类型number类型
  • 引用值:由多个值构成的对象,是特定引用类型的实例,对应的类型是复杂类型(引用类型):Object,引用类型是把数据和功能组合在一起的结果,有点像类。
    例如:
let Fn = {
  a: 0,
  fa() {
    console.log(this.a);
  },
};
//Fn为引用值,Fn的类型为对象类型Object的实例

class P {
  a = 0;
  fa() {
    console.log(this.a);
  }
}
let p = new P(); //p为引用值,引用类型为P
console.log(p.a); //0
p.fa(); //0

Promise是一个引用类型,可以通过new操作符来实例化一个期约实例。new一个期约实例时必须得传一个执行器函数参数。否则会报一个类型错误

//不正确实例化
let p = new Promise(); //TypeError: Promise resolver undefined is not a function
let fe = () => {};
let p = new Promise(fe);
console.log("🚀 ~ file: promise.js ~ line 45 ~ p", p);
//🚀 ~ file: promise.js ~ line 45 ~ p Promise { <pending> }

上面表示期约实例的状态时pending,期约实例可能是一个有状态的对象,有三种:

  • pending 待定
  • resolved 解决
  • rejected 拒绝

这三种状态取决于执行器函数fe,执行器函数fe可以接受两个函数参数,可以命名为resolve和reject,在fe中调用第一个函数参数会把resolve接受的参数作为结果异步返回出去并把期约状态变为resolved,调用第二个函数参数reject会把接受到的参数作为理由异步返出去并把状态改为reject,并抛出错误。fe中第一个第二个函数参数都不调用时,期约实例状态为pending。

//调用第一个参数
let p = new Promise((resolve, reject) => {
  resolve(1);
});
console.log(p);//Promise {<fulfilled>: 1}
//调用第二个参数
let p = new Promise((resolve, reject) => {
  reject(1);
});
console.log(p);
//Promise {<rejected>: 1}
//Uncaught (in promise) 1 //这个错误是在异步队列里的同步代码
						  //trycatch是抓取不到的。

let p = new Promise((resolve, reject) => {
  try { //浏览器依旧报错
    reject(1);
  } catch (error) {}
});
console.log(p);
//Promise {<rejected>: 1}
//Uncaught (in promise) 1 
//不调用第一第二参数
let p = new Promsie(()=>{});
console.log(p) //Promise {<pending>}

所以,期约实例是有状态的,期约实例p的状态只与执行器函数前两个函数参数是否执行有关。注意:执行器函数是同步执行代码,例如:

let fe = () => {
  console.log(10);
  return 11;
};
let p = new Promise((resolve, reject) => {
  console.log(0);
  resolve(fe());
  console.log(2);
});
console.log(3);
console.log(p);
// 0;
// 10;
// 2;
// 3;
//Promise { 11 }

而且一个期约实例的状态只会改变一次,就是调用了两次resolve或reject函数,只会受最先执行的那一个影响。例如:

//调用两次resolve
let p = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve(1);
  }, 2000);

  setTimeout(() => {
    resolve(2);
  }, 1000);
});
console.log(3);
setTimeout(console.log, 3000, p);
console.log(p);
// 3
// Promise { <pending> }
// Promise { 2 } //三秒之后打印。
//调用两次reject
let p = new Promise((resolve, reject) => {
  setTimeout(() => {
    reject(1);
  }, 2000);

  setTimeout(() => {
    reject(2);
  }, 1000);
});
console.log(3);
setTimeout(console.log, 3000, p);
console.log(p);
//3
//Promise {<pending>}
//Uncaught (in promise) 2 第二秒报错
//Promise {<rejected>: 2} 第三秒打印

以上是不调用参数函数状态为pending,调用第一个函数参数则有pending转为resolved,调用第二个参数则由pending转为rejected,并在异步里报错。

2、Promise的方法

Promise类型有自己的静态方法Promise.resolve()和Promise.reject()。
第一个方法Promise.resolve()实例化一个解决的期约。等价于:

let p1 = new Promise((resolve, reject) => resolve());
let p2 = Promise.resolve();
console.log(p1); //Promise {<fulfilled>: undefined}
console.log(p2); //Promise {<fulfilled>: undefined}

因为resolve函数没有传参,所以解决值为undefined。第一个参数为解决值,多余的参数会被忽略。

setTimeout(console.log, 0, Promise.resolve(4, 5, 6));
// Promise <resolved>: 4 

第二个方法Promise.reject()会实例化一个拒绝的期约并异步抛出一个错误。等价于:

let p1 = new Promise((resolve, reject) => reject());
let p2 = Promise.reject(); 

3、Promise实例的方法

前面将来期约实例是有三种状态的,ECMAScript设计实例几种方法可以根据状态来相应的异步执行。这些方法里的函数参数都是异步调用执行的。
Promise.prototype.then()方法
then()方法最多接受两个参数:onResolved 处理函数和 onRejected 处理函数,可选的,期约状态变为resolved时执行onResolved 处理函数,接受的参数为resolve返出的参数,期约状态变为rejected时执行onRejected 处理函数,接受的参数为reject返出的参数,并且会 抓取reject()报的错误。
then()方法的第一个参数为onResolved 处理函数,第二个参数为onRejected 处理函数。
例如:

//期约状态变为resolved时执行onResolved 处理函数
let p = new Promise((resolve, reject) => {
  resolve(1);
  // reject(1);
});
p.then(
  (res) => { //这里执行
    console.log(res);
    console.log("resolve");
  },
  (reject) => {
    console.log(reject);
    console.log("reject");
  }
);
//Promise {<fulfilled>: 1}
//1
//resolve
//期约状态变为rejected时执行onRejected 处理函数
let p = new Promise((resolve, reject) => {
  resolve(1);
  // reject(1);
});
p.then(
  (res) => {
    console.log(res);
    console.log("resolve");
  },
  (reject) => { //这里执行
    console.log(reject);
    console.log("reject");
  }
);
//Promise {<rejected>: 1}
//1
//reject

Promise.prototype.catch()方法
Promise.prototype.catch()方法用于给期约添加拒绝处理程序,这个方法只接收一个参数:onRejected 处理程序。调用它就相当于调用 Promise.prototype.then(null, onRejected);

let p = new Promise((resolve, reject) => {
  // resolve(1);
  reject(1);
});
console.log(p);
// p.then(
//   (res) => {
//     console.log(res);
//     console.log("resolve");
//   },
//   (reject) => {
//     console.log(reject);
//     console.log("reject");
//   }
// );
p.catch((reject) => {
  console.log(reject);
  console.log("reject");
});
//Promise {<rejected>: 1}
//1
//reject

Promise.prototype.finally()方法
Promise.prototype.finally()方法用于给期约添加 onFinally 处理程序,这个处理程序在期约转换为解决或拒绝状态时都会执行。这个方法主要是为解决onResolved 和 onRejected 处理程序冗余代码。只要给该方法添加了处理函数,期约实例状态只要是resolved或rejected,都会异步执行处理函数。但finally不会抓取reject()报的错误。

let p = new Promise((resolve, reject) => {
  // resolve(1);
  reject(1);
});
console.log(p);
p.then(
  (res) => {
    console.log(res);
    console.log("resolve");
  },
  (reject) => {
    console.log(reject);
    console.log("reject");
  }
);
p.finally((fin) => {
  console.log(fin);
  console.log("finally");
});
//Promise {<rejected>: 1}
//1
//reject
//undefined
//finally
//Promise {<rejected>: 1}

4、Promise连锁与合成

连锁即为串联,每个期约实例的方法(then()、catch()和 finally())都会返回一个新的期约对象
回到最初的setTimeout异步回调问题:
使用promise连锁方式解决:

let p = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve(1);
  }, 3000);
});

p.then((res) => {//这里异步执行并依赖执行器函数resolve什么时候调用
  console.log(res);
  return res + 1;
}).then((res) => {//这里也是异步执行并依赖什么时候前一个实例resolve
  console.log(res);
});

Promise.all()
接受一组期约,返回的也是期约实例,等这组所有的期约都解决之后才解决。这一组一个拒绝,则返回的实例也是拒绝状态,一个待定,则返回的实例也是待定。

Promise.race()
也是接收一组期约实例,返回最先拒绝或解决的期约实例。

5、Promise思考题

1、打印什么

console.log(1);
let p = new Promise((resolve, reject) => {
  console.log(2);
  for (let i = 0; i < 10000; i++) {
    if ((i === 5000)) {
      resolve(3);
    }
    if ((i === 4000)) {
      reject(4);
    }
  }
  console.log(5);
});

p.then((res) => {
  console.log(res);
}).catch((rej) => {
  console.log(rej);
});
console.log(6);

2、打印什么

let a = new Promise((resolve) => {
  console.log(1);
  resolve();
}).then(() => {
  console.log(2);
});

setTimeout(() => {
  console.log(3);
});

let b = new Promise(async (resolve) => {
  await a;
  console.log(4);
  await b;
  console.log(5);
  resolve();
}).then(() => {
  console.log(6);
});
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

h沐

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值