直到将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
相关推荐
拉不动的猪1 小时前
都25年啦,还有谁分不清双向绑定原理,响应式原理、v-model实现原理
前端·javascript·vue.js
狂炫一碗大米饭1 小时前
一文打通TypeScript 泛型
前端·javascript·typescript
exploration-earth4 小时前
本地优先的状态管理与工具选型策略
开发语言·前端·javascript
哈贝#4 小时前
vue和uniapp聊天页面右侧滚动条自动到底部
javascript·vue.js·uni-app
Lazy_zheng4 小时前
🚀 前端开发福音:用 json-server 快速搭建本地 Mock 数据服务
前端·javascript·vue.js
用户2519162427114 小时前
ES6之块级绑定
javascript
ZzMemory4 小时前
藏起来的JS(四) - GC(垃圾回收机制)
前端·javascript·面试
林太白5 小时前
前端必会之Nuxt.js
前端·javascript·vue.js
晓晓莺歌5 小时前
vue-router路由问题:可以通过$router.push()跳转,但刷新后又变成空白页面
前端·javascript·vue.js
前端Hardy5 小时前
HTML&CSS:高颜值视差滚动3D卡片
前端·javascript·html