哦豁,心心念的promise原来是这个样子...

242 阅读8分钟

写在前面

  一个月前说好要整理对promise的理解和手写promise代码,到现在都还没来得及兑现,是时候兑现自己的promise了。本文是结合B站的视频和自己的理解对promise的相关知识点进行梳理和总结,如果有需要,大家可以通过传送门自行查阅。后续会继续兑现自己的promise,抽空整理手写promise代码的文章。

  promise相关知识点理解教程(视频):杰哥课堂-2020最好理解的Promise教程

  promise方法的理解(视频): Promise方法

为什么需要promise

  假设有这么一个需求场景,首先需要通过发起请求获取id,再通过获得的id请求接口获取用户名,再根据获得的用户名发起请求获取用户的邮箱。如果使用ajax进行请求,需要在第一个请求的success成功回调中发起第二个请求,再通过在第二个请求的success成功回调中发起第三个请求。如果还需要请求其它数据,这样层层嵌套就形成了回调地狱,promise就是为了解决回调地狱而生,使我们代码更加优雅。

promise的基本使用

  • Promise是一个构造函数,通过new关键字实例化对象。  1. 语法:new Promise((resolve, reject) => {})

 2. 接受一个函数作为参数

 3. 在参数函数中接受两个参数resolvereject

  • Promise实例有两个属性:  1. state:状态

 2. result: 结果

promise的状态(为了区分then方法中的哪个函数被执行)

promise的状态

 第一种状态: pending(准备,待解决,进行中)

 第二种状态: fulfilled(已完成,成功)

 第三种状态: rejected(已拒绝,失败)

promise状态的改变

 如果不调用resolvereject,返回的Promise实例的状态为pending,但是可以通过调用resolve()和reject()改变当前promise对象的状态。调用resolve(),当前promise对象的状态会改为fulfilled,调用reject(),当前promise对象的状态会变化为rejected。

const p = new Promise((resolve, reject) => {
    // resolve(): 调用函数, 使当前promise对象的状态改成fulfilled
    // reject(): 调用函数, 使当前promise对象的状态改成rejected
    resolve()
   // reject()
})
console.dir(p)

  注意: promise状态的改变是一次性的。 这句话的意思就是说,如果尝试多次调用函数去改变当前Promise对象的状态,当前Promise对象的状态只会受第一次调用的函数的影响。比如:

const p = new Promise((resolve, reject) => {
   resolve()
   reject()
})
console.dir(p) //状态为fulfilled

promise状态的补充

  promise的状态不改变, 即promise的状态为pending时,不会执行then里面的方法。

// 如果promise的状态不改变, then里的方法不会执行
new Promise((resolve, reject) => {

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

promise的结果(通过形参去接收)

  如果不调用函数,返回的Promise对象的结果默认为undefined;如果调用了函数,且在函数中传递了参数,那么返回的Promise对象的结果就是函数中所传递的参数。

const p = new Promise((resolve, reject)=> {
  // 通过调用resolve, 传递参数, 改变当前promise对象的结果
  resolve('成功的结果')
  //reject('失败的结果')
})
console.dir(p)

promise的原型对象方法

then方法

  有两个函数参数,分别在成功时和失败时进行调用。返回值同样是一个Promise对象,无论then方法前面的Promise对象的状态是什么,then方法返回的状态都是pending,因此可以对Promise进行then方法的链式调用。

const p = new Promise((resolve, reject)=> {
  // 通过调用resolve, 传递参数, 改变当前promise对象的结果
  resolve('成功的结果')
  //reject('失败的结果')
})

// then方法函数
// 参数
// 1. 是一个函数
// 2. 还是一个函数
// 返回值: 是一个promise对象
p.then(()=>{
  // 当promise的状态是fulfilled时, 执行
  console.log('成功时调用')
}, () => {
  // 当promise的状态是rejected时, 执行
  console.log('失败时调用')
})
console.dir(p) //打印成功时调用
const p = new Promise((resolve, reject)=> {
  // 通过调用resolve, 传递参数, 改变 当前promise对象的 结果
  //resolve('123')
  reject('失败的结果')
})

// then方法函数
// 参数
// 1. 是一个函数
// 2. 还是一个函数
// 返回值: 是一个promise对象
p.then((value)=>{
  // 当promise的状态是fulfilled时, 执行
  console.log('成功时调用', value)
}, (err) => {
  // 当promise的状态是rejected时, 执行
  console.log('失败时调用', err)
})
console.dir(p) //打印失败的结果 失败时调用

then方法的补充

  书接上文,前面已经提到promise对象的then方法返回的是一个pending状态的promise。那么该如何去改变这个promise的状态呢?话不多说,上代码:

  • 将状态由pending改为fulfilled
const p = new Promise((resolve, reject) => {
   // 如果promise的状态不改变, then里的方法也不会执行
  //如果此处不调用resolve()方法,在p.then()方法中也不会调用成功回调
  //继而也不会改变promise对象的then方法返回的状态
  //因此,如果想改变promise对象的then方法返回的状态,这句话必须得写上
  resolve()
})

const t = p.then((value) => {
  console.log('成功')
  // 在成功回调中,使用return可以将t实例的状态从pending改成fulfilled,并传递成功时的参数
  //在返回的Promise对象的成功回调中,可以使用value进行接收所传递的参数
  return 123
}, (reason) => {
  console.log('失败')
})

t.then((value) => {
  console.log('成功2', value)
}, (reason) => {
  console.log('失败')
})
  • 将状态由pending改为rejected  如果在then方法中, 出现代码错误, 会将返回的promise实例改为rejected状态
// 如果promise的状态不改变, then里的方法不会执行
const p = new Promise((resolve, reject) => {
  resolve()
})

const t = p.then((value) => {
  console.log('成功')
  // 使用return可以将t实例的状态改成fulfilled
  //return 123
  // 如果这里的代码出错, 会将t实例的状态改成rejected
  console.log(a)
}, (reason) => {
  console.log('失败')
})

t.then((value) => {
  console.log('成功2', value)
}, (reason) => {
  console.log('失败', reason)
})

catch方法

 1.catch中的参数函数在promise对象的状态改为rejected时会执行触发。

 2.promise执行体中出现代码错误时,也会执行。

const p = new Promise((resolve, reject) =>{
  // reject()
  // console.log(a)
  throw new Error('出错了')
})

// 思考: catch中的参数函数在什么时候被执行?
// 1. 当promise的状态改为rejected时, 被执行
// 2. 当promise执行体中出现代码错误时, 被执行
p.catch((reason) => {
  console.log('失败', reason)
})
console.log(p)

promise最常见的写法

  上文已经提到过,在then方法中可以接收成功和失败的回调。但因为有了catch方法这一件小棉袄,我们最常见的写法是将promise的成功回调放在then方法中执行,将promise的失败回调放在catch方法中执行。

new Promise((resolve, reject) => {

})
    .then((val) => {
        //成功时被执行
        console.log(val)
    })
    .catch((err) => {
        //失败时被执行
        console.log(err)
    })

all方法

 Promise.all()方法用于将多个 Promise 实例,包装成一个新的 Promise 实例。

 用法: const p =Promise.all([p1, p2, p3]); //p1、p2、p3都是promise实例

注意事项:

 1.Promise.all()方法接受一个Promise实例数组作为参数,方法的返回值也是一个promise。这就意味着可以在Promise.all()后使用then方法接收成功回调,使用catch方法接收失败回调。

 2.只有Promise实例数组的状态都为fulfilled时,才会执行Promise.all()后的成功回调。如果Promise实例数组中有一个状态为rejected,就会马上执行Promise.all()后的失败回调,返回第一个rejected的结果,而不会执行成功回调。

  • 成功回调的结果
let p1  = new Promise((resolve, reject) => {
  setTimeOut(resolve, 2000, 'p1')
})

let p2  = new Promise((resolve, reject) => {
  setTimeOut(resolve, 1000, 'p2')
})

let p3  = new Promise((resolve, reject) => {
  setTimeOut(resolve, 3000, 'p3')
})

let pAll = Promise.all([p1, p2, p3])

pAll
  .then(success => { 
      console.log(success)
  })
  .catch(err => {
      console.log(err)
  })
 //返回["p1","p2","p3"]
 //返回值为一个数组
 //返回值的顺序与Promise.all方法中的调用顺序是一致的,而与异步请求的时间快慢无关。
  • 失败回调的结果
let p1  = new Promise((resolve, reject) => {
  setTimeOut(reject, 2000, 'p1')
})

let p2  = new Promise((resolve, reject) => {
  setTimeOut(resolve, 1000, 'p2')
})

let p3  = new Promise((resolve, reject) => {
  setTimeOut(resolve, 3000, 'p3')
})

let pAll = Promise.all([p1, p2, p3])

pAll
  .then(success => { 
      console.log(success)
  })
  .catch(err => {
      console.log(err)
  })
 //返回值为p1
 //一旦有一个promise失败,会直接返回触发失败的回调函数,而不再理会其它的promise是否完成

race方法

 写法和all方法大同小异,方法的返回同样是一个promise对象。当然也可以继续使用then方法触发成功回调,使用catch方法触发失败回调。记住一个原则即可: 谁先完成就根据完成的状态和结果返回谁。

 用法: const p =Promise.race([p1, p2, p3]); //p1、p2、p3都是promise实例

 因为和all方法相差无几,这里就不举栗了。

写在后面的promise

  后续会陆续抽空整理手写promise代码、自定义Zoomout浏览组件、全局防抖以及sku属性筛选,过一遍购物车的逻辑。自己直到现在都没完整地通读一遍vue文档,必须强迫自己通读且多次通篇浏览vue文档(vue全家桶)和vue-element-admin文档,看vue源码,学习token以及axios封装。还是很菜,还是有很多地方学要学习。


                            学习&总结