Promise详解

1,373 阅读6分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第9天,点击查看活动详情

前言

本文主要介绍和总结Promise的作用、使用方式和其对应的一些方法,供大家参考学习,如有写的不准确的地方欢迎大家指出,相互学习,共同进步!

一. 什么是Promise?

JavaScript的世界中,所有代码都是单线程执行的。由于这个“缺陷”,导致JavaScript的所有网络操作,浏览器事件,都必须是异步执行。异步执行可以用回调函数实现

function requestData(url, successCallback, failtureCallback) {
  // 模拟网络请求
  setTimeout(() => {
    // 拿到请求的结果
    // url传入的是localhost, 请求成功
    if (url === "localhost") {
      // 成功
      successCallback('success')
    } else { // 否则请求失败
      // 失败
      failtureCallback("error")
    }
  }, 3000);
}

//执行请求
requestData("kobe", (res) => {
  console.log(res)
}, (err) => {
  console.log(err)
})

但实际开发过程中有些情况需要多次调用服务器API,就会形成一个链式调用,比如为了完成一个功能,我们需要调用API1、API2、API3,依次按照顺序进行调用,这个时候就会出现回调地狱的问题,即嵌套层次深,不好维护,可读性差。这时候就需要用到Promise

二. Promise使用方式

Promise 对象的构造器(constructor)语法如下:

// 传入的这个函数, 被称之为 executor
// > resolve: 回调函数, 在成功时, 回调resolve函数
// > reject: 回调函数, 在失败时, 回调reject函数
let promise = new Promise(function(resolve, reject) { // executor });

executor 最终将 promise 移至以下状态之一:

executor 只能调用一个 resolve 或一个 reject。一旦状态被确定下来,Promise的状态会被锁死,该Promise的状态是不可更改的。 image.png

上方代码改写为Promise:

// request.js
function requestData(url,) {
  // 异步请求的代码会被放入到executor中
  return new Promise((resolve, reject) => {
    // 模拟网络请求
    setTimeout(() => {
      // 拿到请求的结果
      // url传入的是localhost, 请求成功
      if (url === "localhost") {
        // 成功
        resolve(success)
      } else { // 否则请求失败
        // 失败
        reject('error')
      }
    }, 3000);
  })
}

const promise = requestData("localhost")

//then方法是Promise对象上的一个方法:它其实是放在Promise的原型上的 Promise.prototype.then
promise.then((res) => {
  console.log("请求成功:", res)
}, (err) => { 
   console.log("请求失败:", err)
})

//等价于
promise.then((res) => {
  console.log("请求成功:", res)
}).catch(err => {//catch方法也是Promise对象上的一个方法:它也是放在Promise的原型上的 Promise.prototype.catch
console.log("请求失败:", err)
})

当传入resolve不同的值的区别:

情况一:如果resolve传入一个普通的值或者对象,那么这个值会作为then回调的参数;

情况二:如果resolve中传入的是另外一个Promise,那么这个新Promise会决定原Promise的状态;

举例:

new Promise((resolve, reject) => {
  // pending -> fulfilled
  resolve(new Promise((resolve,reject)=>{
    setTimeout(()=>{resolve(111)},1000)
  }))
}).then(res => {
  console.log("res:", res) //111
}, err => {
  console.log("err:", err)
})

情况三:如果resolve中传入的是一个对象,并且这个对象有实现then方法,那么会执行该then方法,并且根据 then方法的结果来决定Promise的状态;

举例:

// 2.传入一个对象, 这个兑现有then方法
new Promise((resolve, reject) => {
  // pending -> fulfilled
  const obj = {
    then: function(resolve, reject) {
      // resolve("resolve message")
      reject("reject message")
    }
  }
  resolve(obj)
}).then(res => {
  console.log("res:", res)
}, err => {
  console.log("err:", err)//reject message
})

三. Promise实例方法

1. then方法

Promise的状态变成fulfilled的时候,then方法可以多次调用(同理状态变成reject的时候,catch也可以被多次调用) :

onst promise = new Promise((resolve, reject) => {
  resolve("hahaha")
})

promise.then(res => {
  console.log("res1:", res)
})

promise.then(res => {
  console.log("res2:", res)
})

promise.then(res => {
  console.log("res3:", res)
})

then方法本身也是有返回值的, 它的返回值是Promise,我们可以进行链式调用。

promise.then(res => {
  return "aaaaaa"
}).then(res => {
  console.log("res:", res) //aaaaaa
  return "bbbbbb"
})

then方法返回的Promise到底处于什么样的状态呢?

then方法中的回调函数本身在执行的时候,那么它处于pending状态;

then方法中的回调函数返回一个结果时,那么它处于fulfilled状态,并且会将结果作为resolve的参数;

then方法抛出一个异常时,那么它处于reject状态;

then传入不同值区别的同上方resolve相同,这边就不举例了,大家自己动手写一下。

2.catch 方法

catch方法也是会返回一个Promise对象的,所以catch方法后面可以继续调用then方法或者catch方法:

const promise = new Promise((resolve, reject) => {
  reject("111111")
})

promise.then(res => {
  console.log("res:", res)
}).catch(err => {
  console.log("err:", err)//111111
  // throw new Error('hhhhhh')
  return "catch return value"
}).then(res => {
  console.log("res result:", res) //catch return value
}).catch(err => {
  console.log("err result:", err)  
})

image.png

提示:把上方注释的throw方法打开,方法会取下方catch部分

3.finally 方法

finally是在ES9(ES2018)中新增的一个特性:表示无论Promise对象无论变成fulfilled还是reject状态,最终都会被执行的代码。

finally方法是不接收参数的,因为无论前面是fulfilled状态,还是reject状态,它都会执行

const promise = new Promise((resolve, reject) => {
  // resolve("resolve message")
  reject("reject message")
})

promise.then(res => {
  console.log("res:", res)
}).catch(err => {
  console.log("err:", err)
}).finally(() => {
  console.log("finally code execute")
})

四. Promise类方法

1.Promise.resolve

用法相当于new Promise,并且执行resolve操作:

// 1.普通的值
 const promise = Promise.resolve({ name: "why" })
// 相当于
 const promise2 = new Promise((resolve, reject) => {
   resolve({ name: "why" })
 })

2.Promise.reject

用法相当于new Promise,只是会调用reject:

const promise = Promise.reject("rejected message")
//相当于
const promise2 = new Promsie((resolve, reject) => {
  reject("rejected message")
})

3.Promise.all

作用是将多个Promise包裹在一起形成一个新的Promise,新的Promise状态由包裹的所有Promise共同决定:

当所有的Promise状态变成fulfilled状态时,新的Promise状态为fulfilled,并且会将所有Promise的返回值 组成一个数组;

当有一个Promise状态为reject时,新的Promise状态为reject,并且会将第一个reject的返回值作为参数;

// 创建多个Promise
const p1 = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve(11111)
  }, 1000);
})

const p2 = new Promise((resolve, reject) => {
  setTimeout(() => {
    reject(22222)
  }, 2000);
})

const p3 = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve(33333)
  }, 3000);
})

// 需求: 所有的Promise都变成fulfilled时, 再拿到结果
// 意外: 在拿到所有结果之前, 有一个promise变成了rejected, 那么整个promise是rejected
Promise.all([p2, p1, p3, "aaaa"]).then(res => {
  console.log(res)
}).catch(err => {
  console.log("err:", err)
})

3.Promise.race

如果有一个Promise有了结果,我们就希望决定最终新Promise的状态,那么可以使用race方法

// 只要有一个Promise变成fulfilled状态, 那么就结束
// 意外: 
Promise.race([p1, p2, p3]).then(res => {
  console.log("res:", res)
}).catch(err => {
  console.log("err:", err)
})

类方法快速总结

  • Promise.all() :要求所有 Promise 成功解析,并且在遇到第一个被拒绝的 Promise 时立即返回被拒绝的 Promise。

  • Promise.allSettled() :则会等待所有 Promise 完成,并返回包含每个 Promise 结果的数组,不论成功与否。

  • Promise.any() 用于等待多个 Promise 中的一个成功,并返回该 Promise 的结果。

  • Promise.race() 用于等待多个 Promise 中的一个完成(无论成功或失败),并返回该 Promise 的结果。

后记

当然promise类远远不止上方几种方法,在ES11,ES12中新增了Promise.allSettledPromise.any, 这边就不介绍了,大家可点击此链接自主学习developer.mozilla.org/enUS/docs/W…