JavaScript中的Promise(个人学习笔记)

分享个人学习Promise时的心得和笔记,想通过这种方式让自己在巩固知识的时候印象更加深刻,同时Promise更加扎实一些,如果有说的不正确的地方也请大佬们帮忙指正一下,十分感谢!

(通过一些碎片化时间进行巩固和分享,持续更新和改进ing……)

Promise是ES6中异步编程的新的解决方案。

Promise是一个构造函数,可以用来封装一个异步操作并可以获取其成功 / 失败的结果值。

在Promise之前,所有的异步操作都是单纯的使用回调函数的方式来解决的

例如:

setTimeout(() =>{}, 2000) // 计时器,它就是使用的回调函数的方式来处理的

还有node.js中fs模块的readFile,以及Ajax请求,都是使用回调函数的方式进行处理。

一 . 为什么要使用Promise?

1 . Promise支持链式调用,可以解决回调地狱的问题。

什么是回调地狱?就是回调函数嵌套调用,例如:

setTimeout(() =>{
    setTimeout(() =>{
        setTimeout(() =>{
            
        }, 2000)
    }, 2000)
}, 2000)

看到这,对于拥有开发经验的人来说已经能够明白了,但是当我还是一个初学者的时候,确实有些难以理解,所以我想讲的更加通俗易懂一些。

// 这里有一个延迟1000毫秒后执行的计时器,我们可以将它当作一个网络请求
// 当我们向后端服务器发起请求的时候,直至服务器返回数据,中间这个过程,需要1000毫秒的时间

setTimeout(() =>{
  // 在服务器响应并且返回了数据后,我们就要在这个回调函数中,做一些我们想要做的逻辑处理
  // ......
}, 1000)

// 假设,当我们拿到这个结果之后,我需要使用这个响应结果作为参数,继续向服务器发起请求
// 所以紧接着,我需要在这个回调函数中,继续发送一个网络请求,这里还是用计时器来进行模拟

setTimeout(() =>{
  setTimeout(() => {
    // 在2000毫秒后,我们得到了第二个网络请求的响应结果,然后在回调函数中继续做逻辑处理
    // ......
  }, 2000)
}, 1000)

// 以此类推,我们需要发送许多个网络请求
// 但是请求时传递给后端的参数需要从前一个网络请求中获取
// 这样代码层级就开始不断向后缩进,形成了回调地狱

回调地狱的缺点也非常明显,代码可读性差,且不便于错误的处理。

什么是链式调用?我个人的理解如下:

const p = new Promise((resolve, reject) => {
  resolve()
})

p
.then(() =>{})
.then(() =>{})
.then(() =>{})
.then(() =>{})


// 要理解Promise的链式调用,可以将代码做如下的改造
// 首先,第一个.then()执行结束后,返回了一个新的Promise
// 将这个Promise的保存给变量result,那么result现在就是一个新的Promise
// 然后调用result的.then()方法,继续执行下去

let result = p.then(()=> console.log('执行第一个then'))
let result1 = result.then(()=> console.log('执行第二个then'))
let result2 = result1.then(() => console.log('执行第三个then'))

// 也就是说,每一个.then()方法,都会返回一个全新的Promise,
// 简化下来也就形成了这种链式调用的结构
p
.then(() =>{})
.then(() =>{})
.then(() =>{})
.then(() =>{})

所以链式调用的方式更加灵活而且非常便于阅读。

二 . 创建一个Promise实例。

// 首先在创建Promise对象的时候,首先要传入一个函数,这个函数有两个参数
// 这两个参数都是函数类型
// 统一我们都定义为resolve 和 reject
// 例如:

const p = new Promise((resolve, reject) => {})

// 在这个Promise对象中,我们可以放入任何的异步操作
// 比如上面那个demo中通过计时器来获取了随机数

const p1 = new Promise((resolve, reject) => {
  setTimeout(() =>{
    let number = Math.ceil(Math.random() * 100)
    if (number >= 50) {
      resolve()
    } else {
      reject()
    }
  }, 2000)
})

// 在Promise包裹的异步操作中,我们可以做自己想要的逻辑处理
// 在处理结束后,一定要给Promise一个结果,也就是说
// 如果成功了,要调用resolve()
// 如果失败了,要调用reject()
// 这样我们才能通过.then()方法对Promise进行后续的操作
// 例如下面的代码:

const p2 = new Promise((resolve, reject) => {
  setTimeout(() =>{
    let number = Math.ceil(Math.random() * 100)
    if (number >= 50) {
      console.log('赢了')
    } else {
      console.log('输了')
    }
  }, 2000)
})

p2.then(() =>{
  console.log('成功了')
}, () => {
  console.log('失败了')
})

// 我在做完逻辑处理以后并没有给出Promise的结果
// 所以这段代码的运行结束后只会打印'赢了'或者'输了'
// 它不会进入到.then()中去,因为Promise没有返回结果
// .then()中接收两个函数,第一个函数处理Promise解析后的逻辑,第二个则处理拒绝的
// 上面的代码中,Promise既没有调用resolve(),也没有调用reject()
// 所以.then()中的代码就不会运行。

当我学习到这个地方的时候我就遇到了第一个问题,例如:在vue项目中,我想要封装一个Promise。

// util.js

const getResult = new Promise((resolve, reject) => {
  setTimeout(() =>{
    let number = Math.ceil(Math.random() * 100)
    console.log(number)
    if (number >= 50) {
      resolve()
    } else {
      reject()
    }
  }, 2000)
})

export {
  getResult
}


// index.vue
// 所以在组件中,我直接引入了getResult

import { getResult } from '../utils/util.js'

// 我想要在某个click事件中,使用getResult.then()来获取随机数并且做后续的逻辑处理
// 结果问题来了,在index.vue挂载后,我发现控制台打印了一个number
// 显然它来自Promise中console.log(number)这段代码
// 我才发现,通过new Promise创建的Promise对象会立即执行其传入的函数
// 即使你还没有使用.then()方法
// 所以,util.js中getResult应该修改成如下的方式:

export function getResult() {
  return new Promise((resolve, reject) => {
    setTimeout(() =>{
      let number = Math.ceil(Math.random() * 100)
      console.log(number)
      if (number >= 50) {
        resolve()
      } else {
        reject()
      }
    }, 2000)
  })
}

// 暴露出去的应该是一个函数,而不是一个Promise,当调用这个函数的时候
// 它再返回一个Promise,这样就可以在特定的时间调用getResult获得结果了

三 . 在then()中获取Promise的处理结果。

const p = new Promise((resolve, reject) => {
  setTimeout(() =>{
    let number = Math.ceil(Math.random() * 100)
    if (number >= 50) {
      resolve(number)
    } else {
      reject(number)
    }
  }, 2000)
})

p
.then((value) =>{
  console.log('第一个then获取的value是', value)
  return value
}, (reason) => {
  console.log('第一个then获取的reason是', reason)
  throw reason 
})
.then((value) =>{
  console.log('第二个then获取的value是', value)
}, (reason) => {
  console.log('第二个then获取的reason是', reason)
})

在这个例子中:

  1. p被创建时,它会在2秒后随机解析或拒绝(其实官方的讲法是解析或者拒绝,我个人觉得以成功或者失败来说也没啥问题,只要能理解这个意思就行,Promise中的函数执行后,必定要给出一个结果,成功或者失败,成功了叫解析,失败了叫拒绝)。
  2. 如果number大于或等于50,p会解析,并且第一个.then()的第一个回调函数会被执行。这个回调函数会打印value并返回它。由于返回了一个值,这个值会成为下一个.then()中第一个回调函数的参数。
  3. 如果number小于50,p会拒绝,并且第一个.then()的第二个回调函数会被执行。这个回调函数会打印reason并使用throw语句抛出一个错误。这个错误会传递给下一个.then()的第二个回调函数(这里有一个问题,如果在此处使用return reason,那么在下一个.then()中,则会执行第一个回调函数,原因在最后)。
  4. 第二个.then()根据第一个.then()的行为来决定执行哪个回调函数。如果第一个.then()返回了一个值,那么第二个.then()的第一个回调函数会被执行。如果第一个.then()抛出了一个错误,那么第二个.then()的第二个回调函数会被执行。

Promise链中的每个.then()都会基于前一个.then()的行为来创建并决定其状态。如果.then()的回调函数返回了一个值,那么这个值会传递给下一个.then()的第一个回调函数;如果.then()的回调函数抛出了一个错误,那么这个错误会传递给下一个.then()的第二个回调函数。

四 . Promise的状态。

在之前的Promise例子中,当通过new Promise创建了一个Promise对象或者说Promise实例后,在运行Promise中的函数时,都会调用resolve()或者reject()返回一个结果,其实 resolve() 和 reject() 就是在修改这个Promise的状态。在Promise的规范中定义了,Promise有三种状态,分别是:pending(进行中)、fulfilled(已完成)、rejected(已拒绝)。其实我也看了很多教程和文章,也有的人说只有两种,分别是fulfilled(已完成)、rejected(已拒绝),其实Promise只有这两种最终状态的说法才对,而pending状态也是Promise的一个重要组成部分,它表示异步操作还未完成,既不是成功也不是失败。如果将pending状态忽略,只考虑fulfilled和rejected,也是不完整的。

pending(进行中):初始状态,既不是成功,也不是失败状态。

fulfilled(已完成):意味着操作成功完成。

rejected(已拒绝):意味着操作失败。

一旦Promise对象的状态改变,就不会再变,也就是说这是一个不可逆的操作。而且在任何时候都可以得到这个结果。这也是Promise的一个重要特点。这使得我们可以依赖Promise的结果,而不用担心它的状态会在后续的操作中发生改变。

举个例子看一下Promise的状态的变化:

const p = new Promise((resolve, reject) => {
  setTimeout(() =>{
    let number = Math.ceil(Math.random() * 100)
    if (number >= 50) {
      resolve(number)
    } else {
      reject(number)
    }
  }, 2000)
})

console.log('p', p)

setTimeout(() => {
  console.log('p', p)
}, 3000)

按照执行顺序来看,首先通过new Promise声明了一个Promise后,Promise中的函数立即开始执行,所以初始化的时候,Promise的状态是pending,2000毫秒后,得到的number是59,经过判断,执行了resolve(),那么Promise的状态被修改为了fulfilled,如果得到的number小于50,那么执行reject(),状态就会变成rejected,Promise的状态其实就是Promise实例对象的一个属性,叫作[PromiseState]。

五 . Promise对象的值。

也就是Promise实例对象的另一个属性,叫作[PromiseResult],它存储了Promise对象成功或者失败后的结果。

通过 resolve() 和 reject() 可以设置这个值,也就是上面的例子中,我们成功后调用resolve(number) 或者 失败后调用reject(number),其实也就是在设置Promise对象的值,一旦赋值,在后续的.then()方法中,就可以通过value 或者 reason将这个值取出来进行相关的操作。

说到这里,就可以先适当的整理一下之前学习的点,做一个小小的总结。

Promise的流程:

1. 当通过new Promise()创建一个Promise对象时,Promise的状态为pending。

2.接着开始执行Promise中的异步操作。

3.如果异步操作成功,调用resolve(),如果异步操作失败,则调用reject()。

4.调用resolve()后,Promise的状态会改为fulfilled,调用reject()后,Promise的状态会改为rejected,注意,Promise的状态,只能从pending变成fulfilled,或者从pending变成rejected,一旦Promise的状态变成fulfilled或者rejected,它的状态将不会再发生变化。

5.在调用.then()方法时,需要传递两个函数,然后根据Promise的状态来决定执行哪一个函数,如果Promise的状态为fulfilled,执行.then()中的第一个函数,如果Promise的状态为rejected,则执行.then()中的第二个函数。

六 . Promise的使用。

const p = new Promise((resolve, reject) =>{
  console.log(1)
})

// 在创建一个Promise对象的时候,需要传入一个函数,这个函数叫作执行器函数。
// 也就是上面代码中 (resolve, reject) =>{ console.log(1) } 这一部分。

console.log(2)

// 通过控制台打印,结果如下:
// 1
// 2

由此可见,执行器函数会在Promise对象创建时立刻调用,而且是同步的。
异步操作会在执行器函数中去执行。

七 . Promise.resolve、Promise.reject 、 Promise.all 和 Promise.race。

注意:稍微开始有一点点绕了,但是一定要捋清楚这个概念。

首先,Promise是一个构造函数,用于创建 Promise 实例,当我们创建Promise实例的时候,传入一个执行器函数,在这个执行器函数中,需要传入resolve 和 reject 两个参数,用于修改Promise的状态。

当前要说的 Promise.resolve、Promise.reject 和 Promise.all,是属于Promise构造函数的静态方法。

所以两个resolve 和 reject千万不能混淆了。

先说Promise.resolve,这个方法返回一个 Promise 对象,这个对象的状态由传入的参数决定。

情况1:

const p1 = Promise.resolve('1')
console.log(p1)
const p2 = Promise.resolve(1)
console.log(p2)
const p3 = Promise.resolve(true)
console.log(p3)

const p4 = Promise.resolve({a: 1, b: 2, c: 3})
console.log(p4)

p4.then(value => {
  console.log('p4 result', value)
})

const p5 = Promise.resolve([1, 2, 3])
console.log(p5)

p5.then(value => {
  console.log('p5 result', value)
})

当传入一个非Promise对象的时候,Promise.resolve会返回一个新的Promise对象,并且状态为fulfilled,可以通过.then()获取到Promise的值进行操作。

情况2:

const p1 = new Promise((resolve, reject) => {
  resolve(1)
})

const p2 = new Promise((resolve, reject) => {
  reject(1)
})

const p11 = Promise.resolve(p1)
const p22 = Promise.resolve(p2)

当传入一个Promise对象的时候,Promise.resolve将不做任何修改,直接返回这个Promise对象。

同时可以看出来,不管我传入的Promise对象的状态是fulfilled还是rejected,Promise.resolve都是原封不动的返回了。

再说Promise.reject,其实跟Promise.resolve的用法相同,当传入一个非Promise对象时,返回一个新的Promise对象,并且状态为rejected,可以通过.catch()获取到Promise的值进行操作。

不同的点在于:

const p1 = new Promise((resolve, reject) => {
  resolve(1)
})

const p2 = new Promise((resolve, reject) => {
  reject(1)
})

const p11 = Promise.reject(p1)
const p22 = Promise.reject(p2)

console.log('p11', p11)
console.log('p22', p22)

神奇的一幕出现了,为什么传入一个Promise对象后,使用Promise.reject创建出来的Promise,它的值居然还是一个Promise,而不是我传入的resolve 或者 reject 的值,这跟Promise.resolve就有了很大的差别,并且如果想要获取到resolve 或者 reject的值,还需要做如下的操作:

const p1 = new Promise((resolve, reject) => {
  resolve(1)
})

const p2 = new Promise((resolve, reject) => {
  reject(1)
})

const p11 = Promise.reject(p1)
const p22 = Promise.reject(p2)

console.log('p11', p11)
console.log('p22', p22)

p11.catch(reason => {
  console.log(reason)
  reason.then(value => {
    console.log('p11 value', value)
  })
})

p22.catch(reason => {
  console.log(reason)
  reason.catch(error => {
    console.log('p12 error', error)
  })
})

只有这样,我才能获取到原始的Promise对象中 resolve 或者 reject 传递的值,于是我迷糊了,查资料去:

这里,Promise.reject 创建了两个新的 Promise,p11 和 p22,并且这两个 Promise 都被拒绝。但是,拒绝的原因分别是 p1 和 p2,而不是我使用resolve 或者 reject传递出来的 1。这意味着 p11 和 p22 的拒绝原因实际上是 Promise 实例,而不是简单的值。

现在,当我调用 .catch 方法来处理这些拒绝的 Promise 时,我得到了拒绝的原因(即原始的 p1 和 p2 Promise 实例),这就是为什么我可以在 .catch 方法的回调中调用 .then 或 .catch。

这段代码存在这样的情况是因为我将一个 Promise 实例作为另一个 Promise 的拒绝原因。在 .catch 方法中,我得到了这个拒绝原因(即原始的 Promise 实例),并且可以在它上面调用 .then 或 .catch 方法,这取决于这个原始 Promise 的状态。这通常不是推荐的做法,因为它可能会导致混淆和难以理解的代码逻辑。如果只是想创建一个新的、状态与原始 Promise 相同 的 Promise,应该使用 Promise.resolve(p) 而不是 Promise.reject(p)

Promise.resolve 和 Promise.reject 小结:

如果想要得到一个状态为失败的 Promise,可以直接使用 Promise.reject(reason),并传入任何您想要作为拒绝原因的值。这个值会作为新 Promise 的拒绝原因,而不会改变其类型。

然而,如果想要基于另一个 Promise 的状态来创建一个新的 Promise,并且希望新 Promise 的状态和值与原始 Promise 相同,那么应该使用 Promise.resolve(promise)Promise.resolve 会保持原始 Promise 的状态,无论是 fulfilled 还是 rejected。如果传入的参数本身就是一个 Promise 实例,Promise.resolve 会直接返回这个实例,而不会创建一个新的 Promise。

再说Promise.all,接收一个参数,参数为Promise组成的数组,返回一个新的Promise,这个新的Promise的状态由Promise数组中每个Promise的状态来决定,如果在这个Promise数组中,所有的Promise执行结果都为成功,那么返回的Promise状态也为成功,并且返回的Promise的值为Promise数组中每个Promise的结果组成的数组,如果Promise数组中有一个Promise失败了,那么返回的Promise状态直接就失败了,并且这个返回的Promise的值就是第一个失败的Promise的结果。这个应该很好理解了,直接上代码:

const p1 = new Promise((resolve, reject) => {
  resolve(1)
})

const p2 = Promise.resolve(2)

const p3 = new Promise((resolve, reject) => {
  resolve(3)
})


const result = Promise.all([p1, p2, p3])

result
.then(value => console.log('value', value))
.catch(reason => console.log('reason', reason))


// value [1, 2, 3]




const p1 = new Promise((resolve, reject) => {
  resolve(1)
})

const p2 = Promise.reject(2)

const p3 = new Promise((resolve, reject) => {
  reject(3)
})


const result = Promise.all([p1, p2, p3])

result
.then(value => console.log('value', value))
.catch(reason => console.log('reason', reason))

// reason 2

最后是Promise.race,接收一个参数,参数也是Promise组成的数组,返回一个新的Promise,数组中第一个完成的Promise的状态和结果,就是新的Promise的状态和结果。这就是Promise数组中每个Promise运行速度的比拼了,谁先运行完成并且改变状态返回结果,新的Promise就继承谁的状态和结果。上代码:

const p1 = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve(1)
  }, 1000)
})

const p2 = Promise.reject(2)

const p3 = new Promise((resolve, reject) => {
  setTimeout(() => {
    reject(3)
  }, 500)
})


const result = Promise.race([p1, p2, p3])

result
.then(value => console.log('value', value))
.catch(reason => console.log('reason', reason))

// reason 2



const p1 = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve(1)
  }, 1000)
})

// const p2 = Promise.reject(2)

const p3 = new Promise((resolve, reject) => {
  setTimeout(() => {
    reject(3)
  }, 500)
})


const result = Promise.race([p1, p3])

result
.then(value => console.log('value', value))
.catch(reason => console.log('reason', reason))

// reason 3

八 . Promise状态的改变。

在之前其实已经学习过了,Promise在初始化的时候状态为 pending ,如果要修改状态,只需要在执行器函数中使用 resolve() 或者 reject() 即可,还有一种方式也可以修改Promise的初始化状态,就是使用throw关键字。

const p = new Promise((resolve, reject) => {
  throw 'Error'
})

p.catch(reason => console.log('reason', reason))
console.log('p', p)

所以,修改Promise状态的方法有三种,

1. pending -> fulfilled, 使用resolve()。

2. pending -> rejected, 使用reject()。

3. pending -> rejected, 使用throw。

九 . Promise的异常穿透。

当使用Promise链在进行链式调用的时候,可以在最后指定失败时候的回调。运行时,前面任何一个节点失败了,都会传递到最后的失败回调中进行操作。

const p = new Promise((resolve, reject) => {
  resolve('success')
})

p
.then(value => {
  console.log('第一次调用then')
  throw 'Error'
})
.then(value => {
  console.log('第二次调用then')
})
.then(value => {
  console.log('第三次调用then')
})
.catch(reason => {
  console.log('reason', reason)
})

// 第一次调用then
// reason Error

十 . 中断Promise链。

const p = new Promise((resolve, reject) => {
  resolve('success')
})

p
.then(value => {
  console.log('第一次调用then')
})
.then(value => {
  console.log('第二次调用then')
})
.then(value => {
  console.log('第三次调用then')
})
.catch(reason => {
  console.log('reason', reason)
})

上面的代码中,如果我在执行完第一个.then()后,不想执行后面的代码了,也就是说要中断这个Promise链,只有一种方式,那就是返回一个pending状态的Promise。

在第一个.then()方法中,加入return new Promise(() => {}),即可中断这个promise链。

原理很简单,如果在第一个.then()中返回一个任意的值,那么它会返回一个成功的Promise对象,如果返回一个失败的Promise或者throw抛出错误,它依旧返回一个失败的Promise,只有一个pending状态的值会阻断后续的.then()方法执行。

比如说,现在定义了一个新的Promise,但是执行器函数中并没有做任何操作,所以它的状态为pending,那么我在调用.then()方法后,它也不会执行,因为Promise的状态没有发生改变,.then()或者.catch()只能是在Promise成功或者失败后才会运行其中的回调函数。利用这一特性就可以中断Promise链。

  • 13
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值