then、catch、finally

发布时间 2023-11-27 16:45:29作者: 柯基与佩奇
  1. Promise 的状态一经改变就不能再改变
  2. .then 和.catch 都会返回一个新的 Promise
  3. catch 不管被连接到哪里,都能捕获上层的错误
  4. 在 Promise 中,返回任意一个非 promise 的值都会被包裹成 promise 对象,例如 return 2 会被包装为 return Promise.resolve(2)
  5. Promise 的 .then 或者 .catch 可以被调用多次, 当如果 Promise 内部的状态一经改变,并且有了一个值,那么后续每次调用.then 或者.catch 的时候都会直接拿到该值
  6. .then 或者 .catch 中 return 一个 error 对象并不会抛出错误,所以不会被后续的 .catch 捕获
  7. .then 或 .catch 返回的值不能是 promise 本身,否则会造成死循环
  8. .then 或者 .catch 的参数期望是函数,传入非函数则会发生值穿透
  9. .then 方法是能接收两个参数的,第一个是处理成功的函数,第二个是处理失败的函数,再某些时候可以认为 catch 是.then 第二个参数的简便写法
  10. .finally 方法也是返回一个 Promise,他在 Promise 结束的时候,无论结果为 resolved 还是 rejected,都会执行里面的回调函数

[toc]

题目一

const promise = new Promise((resolve, reject) => {
  resolve("success1");
  reject("error");
  resolve("success2");
});
promise
  .then((res) => {
    console.log("then: ", res);
  })
  .catch((err) => {
    console.log("catch: ", err);
  });

// "then: success1"

构造函数中的 resolve 或 reject 只有第一次执行有效,多次调用没有任何作用 。验证了第一个结论,Promise 的状态一经改变就不能再改变

题目二

const promise = new Promise((resolve, reject) => {
  reject("error");
  resolve("success2");
});
promise
  .then((res) => {
    console.log("then: ", res);
  })
  .then((res) => {
    console.log("then: ", res);
  })
  .catch((err) => {
    console.log("catch: ", err);
  })
  .then((res) => {
    console.log("then: ", res);
  });

// "catch: " "error"
// "then3: " undefined

验证了第三个结论,catch 不管被连接到哪里,都能捕获上层的错误。

题目三

Promise.resolve(1)
  .then((res) => {
    console.log(res);
    return 2;
  })
  .catch((err) => {
    return 3;
  })
  .then((res) => {
    console.log(res);
  });

// 1
// 2

Promise 可以链式调用,不过 promise 每次调用 .then 或者 .catch 都会返回一个新的 promise,从而实现了链式调用, 它并不像一般任务的链式调用一样 return this
上面的输出结果之所以依次打印出 1 和 2,那是因为 resolve(1)之后走的是第一个 then 方法,并没有走 catch 里,所以第二个 then 中的 res 得到的实际上是第一个 then 的返回值
且 return 2 会被包装成 resolve(2)

题目四

如果把 3 中的 Promise.resolve(1)改为 Promise.reject(1)又会怎么样呢?

Promise.reject(1)
  .then((res) => {
    console.log(res);
    return 2;
  })
  .catch((err) => {
    console.log(err);
    return 3;
  })
  .then((res) => {
    console.log(res);
  });

// 1
// 3

结果打印的当然是 1 和 3,因为 reject(1)此时走的就是 catch,且第二个 then 中的 res 得到的就是 catch 中的返回值

题目五

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

// 'timer'
// success 1001
// success 1002

当然,如果足够快的话,也可能两个都是 1001。 Promise 的 .then 或者 .catch 可以被调用多次,但这里 Promise 构造函数只执行一次。或者说 promise 内部状态一经改变,并且有了一个值,那么后续每次调用 .then 或者 .catch 都会直接拿到该值

题目六

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

可能想到的是进入.catch 然后被捕获了错误。
结果并不是这样的,它走的是.then 里面:
"then: " "Error: error!!!"

这也验证了第 4 点和第 6 点,返回任意一个非 promise 的值都会被包裹成 promise 对象,因此这里的 return new Error('error!!!')也被包裹成了 return Promise.resolve(new Error('error!!!'))

当然如果抛出一个错误的话,可以用下面 ? 两的任意一种:

return Promise.reject(new Error("error!!!"));
// or
throw new Error("error!!!");

题目七

const promise = Promise.resolve().then(() => {
  return promise;
});
promise.catch(console.err);

.then 或 .catch 返回的值不能是 promise 本身,否则会造成死循环。
因此结果会报错:

Uncaught (in promise) TypeError: Chaining cycle detected for promise #<Promise>

题目八

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

其实只要记住原则 8:.then 或者 .catch 的参数期望是函数,传入非函数则会发生值穿透。
第一个 then 和第二个 then 中传入的都不是函数,一个是数字类型,一个是对象类型,因此发生了穿透,将 resolve(1) 的值直接传到最后一个 then 里。
所以输出结果为:1

题目九

下面来介绍一下.then 函数中的两个参数。

第一个参数是用来处理 Promise 成功的函数,第二个则是处理失败的函数。 也就是说 Promise.resolve('1')的值会进入成功的函数,Promise.reject('2')的值会进入失败的函数。

来看看这个例子

Promise.reject("err!!!")
  .then(
    (res) => {
      console.log("success", res);
    },
    (err) => {
      console.log("error", err);
    }
  )
  .catch((err) => {
    console.log("catch", err);
  });

这里的执行结果是:
'error' 'error!!!'

它进入的是 then()中的第二个参数里面,而如果把第二个参数去掉,就进入了 catch()中:

Promise.reject("err!!!")
  .then((res) => {
    console.log("success", res);
  })
  .catch((err) => {
    console.log("catch", err);
  });

执行结果:
'catch' 'error!!!'

如果是这个案例呢?

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

由于 Promise 调用的是 resolve(),因此.then()执行的应该是 success()函数,可是 success()函数抛出的是一个错误,它会被后面的 catch()给捕获到,而不是被 fail1 函数捕获

因此执行结果为:
fail2 Error: error!!!
at success

题目十

接着来看看.finally(),这个功能一般不太用在面试中,不过如果碰到了也应该知道该如何处理。

function promise1() {
  let p = new Promise((resolve) => {
    console.log("promise1");
    resolve("1");
  });
  return p;
}
function promise2() {
  return new Promise((resolve, reject) => {
    reject("error");
  });
}
promise1()
  .then((res) => console.log(res))
  .catch((err) => console.log(err))
  .finally(() => console.log("finally1"));

promise2()
  .then((res) => console.log(res))
  .catch((err) => console.log(err))
  .finally(() => console.log("finally2"));

结果:
'promise1'
'1'
'error'
'finally1'
'finally2'

综合题

题目一

const first = () =>
  new Promise((resolve, reject) => {
    console.log(3);
    let p = new Promise((resolve, reject) => {
      console.log(7);
      setTimeout(() => {
        console.log(5);
        resolve(6);
        console.log(p);
      }, 0);
      resolve(1);
    });
    resolve(2);
    p.then((arg) => {
      console.log(arg);
    });
  });

first().then((arg) => {
  console.log(arg);
});
console.log(4);

第一段代码定义的是一个函数,所以得看看它是在哪执行的,发现它在 4 之前,所以可以来看看 first 函数里面的内容了。
函数 first 返回的是一个 new Promise(),因此先执行里面的同步代码 3
接着又遇到了一个 new Promise(),直接执行里面的同步代码 7
执行完 7 之后,在 p 中,遇到了一个定时器,先将它放到下一个宏任务队列里不管它,接着向下走
碰到了 resolve(1),这里就把 p 的状态改为了 resolved,且返回值为 1,不过这里也先不执行
跳出 p,碰到了 resolve(2),这里的 resolve(2),表示的是把 first 函数返回的那个 Promise 的状态改了,也先不管它。
然后碰到了 p.then,将它加入本次循环的微任务列表,等待执行
跳出 first 函数,遇到了 first().then(),将它加入本次循环的微任务列表(p.then 的后面执行)
然后执行同步代码 4
本轮的同步代码全部执行完毕,查找微任务列表,发现 p.then 和 first().then(),依次执行,打印出 1 和 2
本轮任务执行完毕了,发现还有一个定时器没有跑完,接着执行这个定时器里的内容,执行同步代码 5
然后又遇到了一个 resolve(6),它是放在 p 里的,但是 p 的状态在之前已经发生过改变了,因此这里就不会再改变,也就是说 resolve(6)相当于没任何用处,因此打印出来的 p 为 Promise{: 1}。

3
7
4
1
2
5
Promise{: 1}

题目二

const p1 = new Promise((resolve) => {
  setTimeout(() => {
    resolve("resolve3");
    console.log("timer1");
  }, 0);
  resolve("resovle1");
  resolve("resolve2");
})
  .then((res) => {
    console.log(res);
    setTimeout(() => {
      console.log(p1);
    }, 1000);
  })
  .finally((res) => {
    console.log("finally", res);
  });

注意的知识点:
Promise 的状态一旦改变就无法改变
finally 不管 Promise 的状态是 resolved 还是 rejected 都会执行,且它的回调函数是没有参数的

'resolve1'
'finally' undefined
'timer1'
Promise{: undefined}