直到将Promise的链式调用进行一步步拆解,我才终于发现了Promise设计的精妙之处...

用了Promise整整7年,直到现在将Promise的链式调用进行一步步拆解,我才终于发现了Promise设计的精妙之处,不知道我这7年是怎么过来的?生活从来不缺少美,缺少的是一双能发现美的眼睛。

Promise的详细介绍

promise是一个代理,它代表一个在promise被创建的时还不一定已知的值。它常常跟异步联系在一起,以异步完成后得到对应的值。

Promise有三种状态,分别是:

  • pending(待定):初始状态,既没有兑现,也没有拒绝
  • fulfilled(已兑现):意味着操作成功完成
  • rejected(已拒绝):意味着操作已失败

Promise的状态变化是单向的,也就是只能从初始状态变成已兑现或者已拒绝,不能反过来。

Promise构造函数

Promise的构造函数接收一个参数executor,该参数是一个函数,该函数接收两个参数:

  • resolveFunc 兑现时调用
  • rejectFunc 拒绝时调用

executor函数需要用户自己去实现,该函数在Promise构造函数调用的时候会执行一次,用户可以在函数内部调用异步的方法,比如:

javascript 复制代码
new Promise((resolve, reject)=>{
    let res = 'success'
    setTimeout(()=>{
        resolve(res)
        // 或者
        // reject('发生错误')
    }, 2000)
})

Promise的实例方法

Promise.prototype.then()

then实例方法用于给用户注册已完成状态的回调函数。如果注册的时候,promise对象已经是已完成状态,则会将回调函数微任务队列里进行调用;如果注册的时候,promise兑现还是初始状态,则会在初始状态转变为已完成状态是调用回调函数。

接收的参数:

  • onFulfilled:已兑现状态的回调函数
  • onRejected:已拒绝状态的回调函数

返回的值:

  • 一个新的Promise对象

由于调用then实例方法,每次都会返回一个新的Promise对象,因此就可以实现链式调用了。但是链式调用有个地方让人比较困惑,就是回调函数的返回值,会对then函数的返回的新Promise对象有影响,我们来分析下这个影响,让我们加深对链式调用的理解,从而更好地使用它。

回调函数onFulfilled和onRejected只会调用其中的一个,他们的返回值对then返回的Promise对象的影响是一样的,这里我们已onFulfilled为例来看。

  1. onFulfilled回调函数的返回值是一个promise对象,则then返回的promise对象会随着onFulfilled回调函数返回的promise的状态的改变。

    javascript 复制代码
    let promise0Resolve = null
    let promise0 = new Promise((resolve)=>{
       promise0Resolve  = resolve
    })
    let promise1 = new Promise((resolve, reject)=>{
       resolve(1)
    })
    let promise2 = promise1.then((val)=>{
       console.log('promise1.then, val:',val)
       return promise0
    })
    
    promise2.then((val)=>{
       console.log('promise2.then, val:', val)
    })
    console.log('before promise0 resolve') 
    promise0Resolve(0) 
    console.log('after promise0 resolve') 

    控制台的输出为:

    js 复制代码
     "before promise0 resolve"
     "after promise0 resolve"
     "promise1.then, val:" 1
     "promise2.then, val:" 0

    我们可以看出,promise1.then的返回值是promise2,而promise1.then的onFulfilled回调函数的返回值是 promise1,promise2.then的onFulfilled接收的参数其实就是promise1的resolve传入的值。

  2. onFulfilled回调函数没有返回值(返回undefined)或者返回非promise对象,则then返回的promise对象会立即调用resolve,并传入onFulfilled回调函数返回的值。

    javascript 复制代码
    let promise1 = new Promise((resolve, reject)=>{
       resolve(1)
    })
    let promise2 = promise1.then((val)=>{
       console.log('promise1.then, val:',val)
       return 2
    })
    
    promise2.then((val)=>{
       console.log('promise2.then, val:', val)
    })

    控制台输出为:

    arduino 复制代码
    "promise1.then, val:" 1
    "promise2.then, val:" 2

    可以看出,promise1.then的返回值是promise2,而promise2.then的onFulfilled接收的参数,恰好是promise1.then的onFulfilled回调函数的返回值。如果promise1.then的onFulfilled回调函数没有返回值或者返回undefined,则promise2.then的onFullfiled函数接收的参数也会是undefined。

Promise.prototype.catch()

catch方法其实跟then很像(实际内部也是调用this.then(null, onRejected)来实现的),唯一的区别就是返回的promise的状态不一样,在已拒绝的状态下会执行onRejected。我们来看个例子。

javascript 复制代码
let promise1 = new Promise((resolve, reject)=>{
    resolve(1)
})
let promise2 = promise1.then((val)=>{
    console.log('promise1.then, val:',val)
    throw new Error('promise1.then throw error')
})

promise2.catch((val)=>{
    console.log('promise2.catch, val:', val)
})
//上面的catch等价于:
promise2.then(null, (val)=>{
    console.log('promise2.then onRejected, val:', val)
})

控制台输出为:

vbnet 复制代码
"promise1.then, val:" 1
"promise2.catch, val:" Error: promise1.then throw error
"promise2.then onRejected, val:" Error: promise1.then throw error

结合promise.then和promise.catch,我们来看一个很容易让人遗憾的例子:

javascript 复制代码
let promise1 = new Promise((resolve, reject)=>{
    resolve(1)
})
let promise2 = promise1.then((val)=>{
    console.log('promise1.then, val:',val)
    throw new Error('promise1.then throw error')
})

let promise3 = promise2.then((val)=>{
    console.log('promise2.then, val:', val)
    return 2
})

promise3.catch(val=>{
    console.log('promise3.catch, val:', val)
})

控制台输出:

vbnet 复制代码
 "promise1.then, val:" 1
 "promise3.catch, val:" Error: promise1.then throw error

我们看到,promise3是promise2.then返回的Promise对象,promise2.then的onFulfilled函数并没有被执行,而promise3的catch的回调函数却执行了,这里看着很神奇,让链式调用可以跳过中间的then,然后直接找到最近的一个catch来捕获错误。这就是Promise设计的精妙之处。

我们来看看上面的场景是怎么实现的。在promise2.then里面,我们只注册了onFulfilled回调函数,没有注册onRejected回调函数,这时候promise2.then返回的promise会立即调用reject,并将错误信息传递进来,也就是promise3返回的时候其实已经是已拒绝的状态。所以它会调用promise3.catch注册进来的onRejected函数。

下来我们再来看一种场景:

javascript 复制代码
let promise1 = new Promise((resolve, reject)=>{
    resolve(1)
})
let promise2 = promise1.then((val)=>{
    console.log('promise1.then, val:',val)
    throw new Error('promise1.then throw error')
})

let promise3 = promise2.then(null, (val)=>{
    console.log('promise2.then onRejected, val:', val)
    return 2
})

promise3.catch(val=>{
    console.log('promise3.catch, val:', val)
})

控制台输出:

vbnet 复制代码
 "promise1.then, val:" 1
 "promise2.then onRejected, val:" Error: promise1.then throw error

我们将promise2.then的第一个参数onFulfilled传入null,而第二个参数onRejected则传入一个回调函数,我们发现这个回调函数被执行了,而promise3.catch中的回调函数却没有执行。其原因是传入promise2.then传入onRejected后,promise3会根据onRejected返回的值来操作同步其状态,也就是promise3不在是一开始就是已拒绝状态,因此promise3.catch的回调函数并不会执行。但如果我们改成promise3.then(val=> console.log('promise3.then, val:', val)),我们发现就会有输出了:

vbnet 复制代码
 "promise1.then, val:" 1
 "promise2.then onRejected, val:" Error: promise1.then throw error
 "promise3.then, val:" 2

Promise.prototype.finally()

finally方法的回调函数,无论是已兑现状态或者已拒绝状态都会执行,只不过这个回调函数不接收任何的参数。finally方法也会返回一个Promise对象,该Promise对象的状态和值都会跟前面的then或者catch的值保持一致。

javascript 复制代码
let promise1 = new Promise((resolve, reject)=>{
    resolve(1)
})
let promise2 = promise1.then((val)=>{
    console.log('promise1.then, val:',val)
    throw new Error('promise1.then throw error')
})

let promise3 = promise2.then(null, (val)=>{
    console.log('promise2.then onRejected, val:', val)
    return 2
})

let promise4 = promise3.finally((val)=>{
  console.log('promise3.finally, val:', val)
})

promise4.then(val=>{
  console.log('promise4.then, val:', val)
})

控制台输出:

javascript 复制代码
 "promise1.then, val:" 1
 "promise2.then onRejected, val:" Error: promise1.then throw error
 "promise3.finally, val:" undefined
 "promise4.then, val:" 2
相关推荐
汉堡大王952712 小时前
GPT-6 上线 48 小时,我扒开了 Intelligent UI 的运行机制:DIL、沙箱 Worker 和一个 React 式协调器
前端·javascript·后端
hai_android12 小时前
Chat 聊天模块功能总结
前端·javascript·vue.js
创世虚拟世界13 小时前
我做了一个 3D 虚拟世界基底,基于这个再做 3D 虚拟世界或者元宇宙,会事半功倍
javascript·人工智能·3d·gitee·虚拟现实
Highcharts.js14 小时前
用Highcharts开发动态主从图表|双图联动的时间序列图表示例
javascript·数据库·信息可视化·highcharts·图表开发·动态图表
志尊宝15 小时前
Vue3 零基础每日笔记(076):防抖节流的正确使用——搜索框、按钮防重复提交、滚动监听
javascript·vue.js·笔记
能源革命15 小时前
Vue 网格拖拽布局组件全景指南:7 大方案深度对比
前端·javascript·vue.js
雷工笔记16 小时前
KingFusion 驾驶舱开发:ECharts小时模板实现动态时间窗口
javascript
知野小兔16 小时前
JavaScript中Map和Set详解
javascript
daols8816 小时前
vue 表格组件 vxe-table 实现自定义多行编辑功能
前端·javascript·vue.js·vxe-table