【面试题】面试官:为什么Promise中的错误不能被try catch?_axios promise(1)

  • pending: initial state, neither fulfilled nor rejected.
  • fulfilled: meaning that the operation was completed successfully.
  • rejected: meaning that the operation failed.

一个fulfilled Promise有一个fulfillment值,而rejected Promise则有一个rejection reason

为什么要引入Promise?

异步处理在我们日常开发中是很常见的场景,在Promise出现之前,我们都是通过回调来处理异步代码的结果,但是出现了一些问题:

  • 回调地狱,在有多个异步逻辑存在依赖关系时,我们只能在回调里嵌套,这些深度嵌套的代码让代码难以阅读和维护,业界称之为回调地狱
  • 回调也没用标准的方式来处理错误,大家都凭自己的喜好来处理错误,可能我们使用的库跟api都定义了一套处理错误的方式,那我们把多个库一起搭配使用时,就需要花额外的精力去把他们处理皮实
  • 有时候我们需要对一个已经完成的逻辑注册回调。这也没有统一的标准,对于大部分代码,我们根本就不能对这些已经执行完的代码注册回调,有些会同步执行回调,有些会异步执行回调,我们根本不可能记住所有api的机制,要么每次使用时我们都要研究这个api的实现机制,要么我们可能就在写bug
  • 而且,如果我们想对一个异步逻辑注册多个回调,这也要看api提供方支不支持
  • 最重要的,如果有统一的方式来处理错误跟正确结果的话,我们就有可能实现一套通用的逻辑来简化代码复杂度,这种自己发挥的情况就很难

是的,Promise的出现就是为了解决这所有的问题。

怎么创建Promise

Promise构造函数

Promise有一个构造函数,接收一个函数作为参数,这个传入构造函数里的函数被称作executor。 Promise的构造函数会同步地调用executorexecutor又接收resolve函数跟reject函数作为参数,然后我们就可以通过这两个函数俩决定当前Promise的状态(resolve进入fulfilled或者reject进入rejected)。

我们在resolve Promise时,可以直接给它一个值,或者给它另外一个Promise,这样最终是fulfilled还是rejected将取决于我们给它的这个Promise最后的状态。

假如我们现在有一个promise a

  • 如果我们在promise a里面调用resolve,传入了另一个promise bpromise a的状态将取决于promise b的执行结果
  • 如果我们直接传给resolve一个普通的值,则promise a带着这个值进入fulfilled状态
  • 如果我们调用reject,则promise a带着我们传给reject的值进入rejected状态

Promise在一开始都是pending状态,之后执行完逻辑之后变成settled(fulfilled或者rejected)settled不能变成pendingfulfilled不能变成rejectedrejected也不能变成fulfilled。总之一旦变成settled状态,之后就不会再变了。

我们也不能直接拿到Promise的状态,只能通过注册handler的方式,Promise会在恰当的时机调用这些handlerJavaScript Promise可以注册三种handler

  • then 当Promise进入fulfilled状态时会调用此函数
  • catch 当Promise进入rejected状态时会调用此函数
  • finallyPromnise进入settled状态时会调用此函数(无论fulfilled还是rejected

这三个handler函数都会返回一个新的Promise,这个新的Promise跟前面的Promise关联在一起,他的状态取决于前面Promise状态以及当前handler的执行情况。

我们先来看一段代码直观感受下:

function maybeNum() {
  // create a promise
  return new Promise((resolve, reject)=>{
    console.info('Promise Start')
    setTimeout(()=>{
      try{
        const num=Math.random();
        const isLessThanHalf=num<=0.5;
        if(isLessThanHalf){
          resolve(num)
        }else{
          throw new Error('num is grater than 0.5')
        }
      }catch (e) {
        reject(e)
      }
    },100)
    console.info('Promise End')
  })
}

maybeNum().then(value => {
  console.info('fulfilled',value)
}).catch(error=>{
  console.error('rejected',error)
}).finally(()=>{
  console.info('finally')
})
console.info('End')
复制代码

maybeNum函数返回了一个PromisePromise里面我们调用了setTimeout做了一些异步操作,以及一些console打印。

出现的结果类似这样:

Promise Start
Promise End
End
fulfilled 0.438256424793777
finally
复制代码

或者这样:

Promise Start
Promise End
End
rejected Error: num is grater than 0.5 ...
finally
复制代码

我们可以发现,除了setTimeout里的部分,其它都是同步按顺序执行的,所以Promise本身并没有做什么骚操作,它只是提供了一种观察异步逻辑的途径,而不是让我们的逻辑变成异步,比如在这里我们自己实现异步逻辑时还是要通过调用setTimeout

此外,我们还可以通过Promise.resolvePromise.reject来创建Promise

Promise.resolve

Promise.resolve(x)等价于

x instanceof Promise?x:new Promise(resolve=>resolve(x))
复制代码

如果我们传给它的参数是一个Promise,(而不是thenable,关于什么是thenable我们稍后会讲)它会立即返回这个Promise,否则它会创建一个新的Promiseresolve的结果为我们传给它的参数,如果参数是一个thenable,那会视这个thenable的情况而定,否则直接带着这个值进入fulfilled状态。

这样我们就可以很轻松地把一个thenable转换为一个原生的Promise,而且更加方便的是如果有时候我们不确定我们接收到的对象是不是Promise,用它包裹一下就好了,这样我们拿到的肯定是一个Promise

Promise.reject

Promise.reject等价于

new Promise((resolve,reject)=>reject(x))
复制代码

也就是说,不管我们给它什么,它直接用它reject,哪怕我们给的是一个Promise

Thenable

JavaScript Promise的标准来自Promise/A+,,所以JavaScriptPromise符合Promise/A+标准,但是也增加了一些自己的特性,比如catchfinally。(Promise/A+只定义了then

Promise/A+里面有个thenable的概念,跟Promise有一丢丢区别:

  • A “promise” is an object or function with a then method whose behavior conforms to [the Promises/A+ specification].
  • A “thenable” is an object or function that defines a then method.

所以Promisethenable,但是thenable不一定是Promise。之所以提到这个,是因为互操作性。Promise/A+是标准,有不少实现,我们刚刚说过,我们在resolve一个Promise时,有两种可能性,Promise实现需要知道我们给它的值是一个可以直接用的值还是thenable。如果是一个带有thenable方法的对象,就会调用它的thenable方法来resolve给当前Promise。这听起来很挫,万一我们恰好有个对象,它就带thenable方法,但是又跟Promise没啥关系呢? 这已经是目前最好的方案了,在Promise被添加进JavaScript之前,就已经存在很多Promise实现了,通过这种方式可以让多个Promise实现互相兼容,否则的话,所有的Promise实现都需要搞个flag来表示它的PromisePromise

再具体谈谈使用Promise

刚刚的例子里,我们已经粗略了解了一下Promise的创建使用,我们通过then``catch``finally来“hook”进Promisefulfillmentrejectioncompletion阶段。大部分情况下,我们还是使用其它api返回的Promise,比如fetch的返回结果,只有我们自己提供api时或者封装一些老的api时(比如包装xhr),我们才会自己创建一个Promise。所以我们现在来进一步了解一下Promise的使用。

then

then的使用很简单,

const p2=p1.then(result=>doSomethingWith(result))
复制代码

我们注册了一个fulfillment handler,并且返回了一个新的Promise(p2)p2fulfilled还是rejected将取决于p1的状态以及doSomethingWith的执行结果。如果p1变成了rejected,我们注册的handler不会被调用,p2直接变成rejectedrejection reason就是p1rejection reason。如果p1fulfilled,那我们注册的handler就会被调用了。根据handler的执行情况,有这几种可能:

  • doSomethingWith返回一个thenablep2将会被resolve到这个thenable(取决于这个thenable的执行情况,决定p2fulfilled还是rejected
  • 如果返回了其它值,p2直接带着那个值进入fulfilled状态
  • 如果doSomethingWith中途出现throwp2进入rejected状态

这词儿怎么看着这么眼熟?没错我们刚刚介绍resolvereject时就是这么说的,这些是一样的行为,在我们的handlerthrow跟调用reject一个效果,returnresolve一个效果。

而且我们知道了我们可以在then/catch/finally里面返回Promiseresolve它们创建的Promise,那我们就可以串联一些依赖其它异步操作结果且返回Promise的api了。像这样:

p1.then(result=>secondOperation(result))
  .then(result=>thirdOperation(result))
  .then(result=>fourthOperation(result))
  .then(result=>fifthOperation(result))
  .catch(error=>console.error(error))
复制代码

其中任何一步出了差错都会调用catch

如果这些代码都改成回调的方式,就会形成回调地狱,每一步都要判断错误,一层一层嵌套,大大增加了代码的复杂度,而Promise的机制能够让代码扁平化,相比之下更容易理解。

catch

catch的作用我们刚刚也讨论过了,它会注册一个函数在Promise进入rejected状态时调用,除了这个,其他行为可以说跟then一模一样。

const p2=p1.catch(error=>doSomethingWith(error))
复制代码

这里我们在p1上注册了一个rejection handler,并返回了一个新的Promise p2p2的状态将取决于p1跟我们在这个catch里面做的操作。如果p1fulfilled,这边的handler不会被调用,p2就直接带着p1fulfillment value进入fulfilled状态,如果p1进入rejected状态了,这个handler就会被调用。取决于我们的handler做了什么:

  • doSomethingWith返回一个thenablep2将会被resolve到这个thenable
  • 如果返回了其它值,p2直接带着那个值进入fulfilled状态
  • 如果doSomethingWith中途出现throwp2进入rejected状态

没错,这个行为跟我们之前讲的then的行为一模一样,有了这种一致性的保障,我们就不需要针对不同的机制记不同的规则了。

这边尤其需要注意的是,如果我们从catch handler里面返回了一个non-thenable,这个Promise就会带着这个值进入fulfilled状态。这将p1rejection转换成了p2fulfillment,这有点类似于try/catch机制里的catch,可以阻止错误继续向外传播。

这是有一个小问题的,如果我们把catch handler放在错误的地方:

someOperation()
    .catch(error => {
        reportError(error);
    })
    .then(result => {
        console.log(result.someProperty);
    });
复制代码

这种情况如果someOperation失败了,reportError会报告错误,但是catch handler里什么都没返回,默认就返回了undefined,这会导致后面的then里面因为返回了undefinedsomeProperty而报错。

Uncaught (in promise) TypeError: Cannot read property 'someProperty' of undefined
复制代码

由于这时候的错误没有catch来处理,JavaScript引擎会报一个Unhandled rejection。 所以如果我们确实需要在链式调用的中间插入catch handler的话,我们一定要确保整个链路都有恰当的处理。

finally

我们已经知道,finally方法有点像try/catch/finally里面的finally块,finally handler到最后一定会被调用,不管当前Promisefulfilled还是rejected。它也会返回一个新的Promise,然后它的状态也是根据之前的Promise以及handler的执行结果决定的。不过finally handler能做的事相比而言更有限。

function doStuff() {
    loading.show();
#### 最后更多分享:**前端字节跳动真题解析**

**[开源分享:【大厂前端面试题解析+核心总结学习笔记+真实项目实战+最新讲解视频】](https://bbs.csdn.net/topics/618166371)**

- ![](https://img-blog.csdnimg.cn/img_convert/223b8a7b4279184bf64cb5f59f132666.webp?x-oss-process=image/format,png)

mise`,然后它的状态也是根据之前的`Promise`以及`handler`的执行结果决定的。不过`finally handler`能做的事相比而言更有限。



function doStuff() {
loading.show();

最后更多分享:前端字节跳动真题解析

开源分享:【大厂前端面试题解析+核心总结学习笔记+真实项目实战+最新讲解视频】

  • [外链图片转存中…(img-jpkKZdmn-1714513511401)]
  • 24
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值