由于前文提过的回调函数的缺点,因此出现promise语法
promise默认的两个词是resolve和reject,而且由于链式操作,也不会出现回调地狱,也可以在then方法中定于对于错误的处理方法。

  1. let _promise = new Promise((resolve, reject) => {
  2. resolve("成功");
  3. // reject("失败");
  4. });
  5. console.log(_promise);

promise的状态

传入构造函数中的是executor’执行者’,意为控制promise的状态,如上是成功状态。
image.png
promise有三个状态

  • pending: 初始状态,既不是成功,也不是失败状态。
  • fulfilled: 意味着操作成功完成。
  • rejected: 意味着操作失败。

executor中的函数会改变promise的状态。
注意:promise的状态是单向的、不可改变的,如果同时又两个状态,比如resolve和reject,只会按照第一个函数来,后面的不会改变状态。

不同promise状态下的任务

使用then()方法来分别控制不同状态下的执行任务。resolve和reject会调用then中不同状态下的方法
注意then中的方法可以传入reject里面的参数,如下

  1. let _promise = new Promise((resolve, reject) => {
  2. // resolve("成功");
  3. reject("失败");
  4. });
  5. _promise.then(
  6. () => {
  7. console.log("成功啦");
  8. },
  9. (data) => {
  10. console.log("失败啦"+data);
  11. }
  12. );
  13. console.log(_promise);

输出结果如下
image.png

同步任务、宏任务(异步任务)与微任务

在这里先提几个概念。
之前说到异步时提到异步任务的任务队列与主进程是不同的,而异步任务的这个任务队列被称为宏任务。
那么现在我们一共有三个不同的任务进程了。分别是同步任务、宏任务、微任务,那么他们的执行顺序究竟是怎么样的呢。
让我们来看看下面的代码

  1. setTimeout(() => {
  2. console.log("宏任务");
  3. }, 0);
  4. let _promise = new Promise((resolve, reject) => {
  5. resolve();
  6. console.log("chenggong");
  7. });
  8. _promise.then(
  9. () => {
  10. console.log("成功啦");
  11. },
  12. () => {
  13. console.log("失败啦");
  14. }
  15. );
  16. console.log(_promise);

上面的代码输出的结果是
image.png
可以看到,executor中的代码是作为同步任务进行的,而且执行顺序是同步任务>微任务>宏任务。

promise的工作原理

promise在构造函数执行的时候,就会同步执行executor中的代码,并改变promise的状态。
在改变状态的同时,如果是成功状态,则会安排then中的成功方法进入微任务队列,相应的,失败状态则会安排失败方法进入微任务队列。
这就解释了上面的代码的执行顺序,首先进行同步任务的执行,输出executor中的chenggong和_promise变量,然后进行微任务,输出成功状态的’成功啦’,最后输出宏任务中的’宏任务’。
看个例子

  1. let _promise = new Promise((resolve, reject) => {
  2. setTimeout(() => {
  3. resolve();
  4. console.log("chenggong");
  5. }, 0);
  6. });
  7. _promise.then(
  8. () => {
  9. console.log("成功啦");
  10. },
  11. () => {
  12. console.log("失败啦");
  13. }
  14. );
  15. console.log(_promise);

这个例子将executor中的resolve函数放入了异步函数中,这样resolve就不会在同步线程中执行,因此也不会安排then中的函数进入微任务队列,因此输出结果如图。
不过值得注意的是,虽然promise的状态是pending,但是promisestatus却是resolved,让我费解。
image.png

promise.reject

promise.reject返回一个拒绝状态的promise,下例是通过创建一个error实例来传递error信息。

  1. Promise.reject(new Error('fail')).then(() =>
  2. {
  3. }, (error) =>
  4. {
  5. console.log(error);
  6. })

promise.all

promise.all(iterable) 传入的参数为可迭代对象,会返回一个数组(返回的promise的状态的结果),包含所有参数(包括promise或者是非promise)的完成状态的值(失败或者成功),有以下几种情况
1.传入空,promise会返回已完成状态的promise对象
2.传入的参数中没有promise,会返回异步完成状态的promise
3.其他情况会返回一个pending状态的promise,这个promise会在参数全部状态确定时,返回成功或者失败的值,顺序为原来的参数的顺序,注意只要有一个失败即返回失败状态的promise
成功的时候数组中是全部的成功的值,而失败的时候,返回的是最先失败的结果

promise.race

promise.race(iterable) race为比赛,意思就是传入的可遍历的参数中的promise进行速度上的比赛,只要有一个已经完成,那么就会调用后续对应的then中的方法。

  1. let p1 = new Promise((resolve, reject) => {
  2. setTimeout(() => {
  3. resolve('success')
  4. },1000)
  5. })
  6. let p2 = new Promise((resolve, reject) => {
  7. setTimeout(() => {
  8. reject('failed')
  9. }, 500)
  10. })
  11. Promise.race([p1, p2]).then((result) => {
  12. console.log(result)
  13. }).catch((error) => {
  14. console.log(error) // 打开的是 'failed'
  15. })

promise.allSettled

与promise.all相似,promise.allSettled接收一组promise实例
只有等到所有的promise的实例都结束返回状态,promise.allSettled才会返回fullfilled,而且只会变成fullfilled
如果传入的不是promise实例,则会调用promise.resolve将其转化成实例
promise.allSettled会返回一个数组,数组的成员依原promise的状态而决定,成功的promise会返回status为fullfilled,并包含value的对象,失败的promise会返回status为rejected,并包含失败原因reason的对象。
promise.allSettled可以用于确定所有所有的promise都已经结束

promise.finally

写在promise链条的最后一环,意思是不论promise的结果为什么,都会执行finally里面的代码
finally不接受任何参数,应该是一段不依赖promise状态的代码

promise.catch

一般来说promise可以使用第二个回调函数来处理reject,不过也可以在promise的后面跟上catch来处理错误(这种更好)
因为then的第二个参数只能处理前一轮的promise,而catch可以监听之前所有的,而且更易阅读
与try、catch不同的是,catch方法必须指定处理错误的回调函数,否则不会影响外部的代码运行
catch后也可以跟then方法,也可以跟catch方法继续捕获前一个catch的错误

promise的链式操作

promise的then方法会返回一个新的promise的对象,这个对象的状态由上一个then的方法所决定,因此可以进行链式操作。链式操作不仅会向后传递状态,也会传递值。

async关键字

async关键字其实是promise的语法糖,用来将function转化成一个异步函数,并返回promise对象。
如下,因此也可以使用then,catch等链式操作。
image.png

await关键字

await函数只在异步函数内部起作用
async和await关键字可以使用在类或者对象的方法上。
语法

  1. [返回值] = await promise表达式;

这句话的意思是,等待promise表达式运行,如果成功,则会返回promise表达式中resolve回调函数的参数。如果失败,则会抛出异常,异常的值为reject的回调函数的参数,因此可以使用try catch语法。

  1. async function f() {
  2. try {
  3. var a = await Promise.reject(30);
  4. } catch (error) {
  5. console.log(errot); // 30
  6. }
  7. }
  8. f();

在promise完成后,会继续后续的代码。await关键字允许其他任务的继续运行,但是异步函数中的后续代码将会被阻塞。

解决方案

如果同时有几个await关键字,会阻塞后续的代码大量时间,但是将promise储存在变量中,await将同时启动他们

  1. async function timeTest() {
  2. await timeoutPromise(3000);
  3. await timeoutPromise(3000);
  4. await timeoutPromise(3000);
  5. }
  6. async function timeTest() {
  7. const timeoutPromise1 = timeoutPromise(3000);
  8. const timeoutPromise2 = timeoutPromise(3000);
  9. const timeoutPromise3 = timeoutPromise(3000);
  10. await timeoutPromise1;
  11. await timeoutPromise2;
  12. await timeoutPromise3;
  13. }

如上,第一种方案需要等待9s,第二种方案只需要3s。