一篇文章了解Promise,细节拉满!!!

Promise是JavaScript中用于处理异步操作的重要工具,它旨在解决回调地狱问题,提供更加清晰的代码结构。通过then方法实现链式调用,使得错误处理更加集中且易于理解。此外,Promise的异常捕获机制能够确保错误在链中传播,直到被catch捕获。同时,Promise遵循特定的异步执行顺序,与事件循环和任务队列紧密相关。本文深入探讨了Promise的工作原理及其在异步编程中的作用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

为什么要有Promise

“回调地狱”这个词大家应该不陌生,Promise的存在很大一部分原因也是要为我们解决回调地狱的问题,但是这个词可能不是你想的那么简单。

首先多层嵌套这一个因素会导致代码稍微难以读懂,我这里为什么要用“稍微”呢,因为回调地狱并不仅仅是因为嵌套。

更多的是因为传统的嵌套回调中的方法不能只考虑正常执行,要考虑可能导致步骤执行顺序偏离异常的情况,如果我们强行使用手工硬编码(将错误处理方式直接嵌入到程序)来处理,加之有多层嵌套,最终肯定会导致代码的复杂,以至于无法维护和更新。

这句话对我而言算是醍醐灌顶,因为我之前将回调地狱仅仅理解为多层嵌套导致代码不易读。

Promise如何解决问题

  • “神奇”的then方法

    每次Promise对象调用then()都会创建并返回一个新的Promise,这代表着什么呢?你可以在then()方法后面继续调用then(),上一个Prmoise的参数也可以链式传递,这也更符合人类线性思维的方式。

    var p = Promise.resolve(21)
    p.then(function (v) {
        console.log(v) // 21
        return v++
    }).then(function(v) {
        console.log(v) // 22
    })...
    

    这样就完美解决了层层嵌套的问题,如果后面的步骤需要等待前面步骤来完成一些事情(比如网络访问)后再调用,要怎么处理呢?

    var p = Promise.resolve(21)
    p.then(function (v) {
        console.log(v) // 21
        return new Promise( function(resolve, reject) {
            // 一些耗时操作
            resolve(v++)
        })
    }).then(function(v) {
        console.log(v) // 22
    })...
    

    使用retuan语句可以立即完成链接的Promise,可以理解为“顶替”了then()方法本来要反馈的promise.

    • 特殊的异常处理

      传统的try...catch只能处理同步的错误。

      function foo() {
          setTimeout( function() {
              obj.error(); // obj并未提前声明,此处会抛出异常
          } )
      }
      try {
          foo()
      } catch(err) {
          // 上述代码的错误会抛出全局的错误,并不会在catch中被捕获
      }
      

      Promise的异常捕获机制,promise一旦决议,即使在success中产生的异常,也不会触发error函数,这也是promise结果不可变的特性所在,错误会传递给链中的下一个promisecatch作为异常捕获点,会在最终将错误捕获。为了避免对上述解释产生一些误解,请参照下方实践:

      1catch接收成功函数错误信息
      var p = Promise.resolve(20)
      p.then(function success(res) {
          console.log(res.toLowerCase())
      },function error(){
          // 不会在这里报错
      }).catch(err => {
          console.log(err) // TypeError: res.toLowerCase is not a function
      })
      
      2catch接收上一个promise抛出的异常
      var p = Promise.resolve(20)
      p.then(function success(res) {
          console.log(res.toLowerCase())
      }).then(function success(res) {},
              function error(res){return res})  // 第一个then报错信息会触发本次promise,导致触发错误的回调函数
        .catch(err => { // 最终捕获上一个promise(第二个then)抛出的异常
          console.log(err) // Promise {<fulfilled>: TypeError: res.toLowerCase is not a function
      }) 
      
      3catch接收上一个promise默认抛出的异常
      如果没有定义error函数呢?如下所示,默认的处理函数会把错误继续冒泡至外层promise[此处为catch函数]
      var p = Promise.resolve(20)
      p.then(function success(res) {
          console.log(res.toLowerCase())
      }).then(function success(res) {})  
        .catch(err => { 
          console.log(err) // TypeError: res.toLowerCase is not a function
      })
      

关于Promise函数异步执行的顺序

一个Promise决议后,这个Promise上所有通过.then注册的回调都会在下一个异步时机点上被立即调用。

关于异步时机点,我们知道JavaScript是有事件循环队列的,在es6中有一个新的概念建立在事件循环队列之上,叫做任务队列。他是挂载时间循环队列每个tick之后的队列,直观点理解就是当前事件循环执行完成之后,可能出现的异步动作(Promise)不会添加到事件循环队列中,而是添加到当前事件的任务队列中。

事件循环(宏,一般包括同步代码,IO,setTimeout , setInterval )和任务队列(微,可以理解为特指Promise)执行规则如下:

执行宏任务循环 -> 执行微任务队列 -> UI render(可选) -> 执行下一个宏任务循环 -> 执行微任务队列 …以此顺序,具体实践请查看下方代码:

console.log('A') // 同步代码,第一轮队列中马上执行
setTimeout(function() {
    console.log('B') // 此代码会加入第二轮宏任务循环
},0);
Promise.resolve().then(function() {
    console.log('C') // promise,第一轮微任务执行
    setTimeout(function() {
        console.log('R') // 此代码加入第二轮宏任务
    },0)
}).then(function() {
    console.log('D') // promise,第一轮微任务执行
}) // 因为没有其他异步操作,所以都会直接加入任务队列中,如果一直.then下去,甚至可以无限循环,无法转移到下一个事件循环中
console.log('E') // 同步代码,第一轮队列执行
按照上述解释,打印的结果分别应为 A E C D B R

参考文献:

深入浅出任务队列机制

《你不知道的JavaScript》 中卷

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值