一文带你由浅到深了解Promise

前言

一直以为自己已经掌握了promise,但最近摸🐟想自己手写一个promise的时候,连实现链式调用都写不出来,加上前阵子去面试的时候,被问到promise的频率非常高,等到自己真正被问懵的时候才知道自己对promise的理解并不够透彻,因此想到写下这篇文章,让自己对Promise有更深刻的理解👍
在这里声明一下,本人从入坑前端以来系统知识的学习都是看coderwhy老师的课程,因此本篇文章内容主要来自coderwhy老师的javascript高级教学视频加上一些小的知识点补充,再用自己的话表达出来,有什么不正确的地方望大佬指正👣

一、Peomise解决的痛点

鲁迅先生曾曰:'新技术的出现必定是为了解决旧技术的某些痛点'

我们都知道,在Promise出现之前,我们对异步任务的处理,一般是通过回调来实现。以下面这段代码为例,我们封装了一个函数,用定时器⏰来模拟异步任务,我们希望在某些条件成立的时候执行一些成功或者失败的操作,ES6 之前,处理异步函数的代码都是这样包装的

   function asyncCode(couter, successCallback, failureCallback) {
      setTimeout(() => {
        if (counter > 0) {
          let total = 0
          for (let i = 0; i < counter; i++) {
            total += i
          }
          successCallback(total)
        } else {
          failureCallback(`${counter}不符合`)
        }
      }, 300)
    }
    asyncCode(10, (total) => {
      console.log(total)
    }, (errmsg) => {
      console.log(errmsg)
    })  //45

在上面这个例子中,我们确实可以解决通过请求函数得到结果之后执行相应的回调函数,得到想要的结果,但这种方法会存在以下几种问题

  • 第一,我们需要自己设计回调函数,回调函数的名称,回调函数的使用
  • 第二,基于第一点,每个人设计出来的回调方案都是不同的,我们需要耐心看别人的源码或者文档,才可以理解这个函数是怎么使用的,耗时耗精力
  • 第三,当我们异步任务(比如异步网络请求任务)非常多的时候,会在一个函数里嵌套很多个回调函数,形成回调地狱,造成代码可读性和维护性很差👎

🥳Promise的出现解决了这些问题,下面我们开始正式进入Promise的学习伐

二、什么是Promise

promise概念

mdn对Promise的解释是是一个对象,用来表示一个异步操作的最终完成(或失败)及其结果值,它能够把异步操作最终的成功返回值或者失败原因和相应的处理程序关联起来,使得异步方法可以像同步方法那样有返回值,异步方法不会立即返回最终的值,而是会返回一个 promise,以便在未来某个时候把值交给使用者

这段解释可能有点抽象官方,其实就是当我们的异步操作执行完成后,这时候会给予调用者一个承诺:待会我会给你回调数据,你可以拿到这个数据再去做其他操作,以此创建出一个Promise对象

promise状态详解

一个Promise必然会处于以下三个状态之一:

  • pending:初始状态,既没有被兑现,也没有被拒绝;
  • fullfilled:意味着操作成功完成;
  • rejected:意味着操作失败;

我们通过代码解释一下这三种状态在代码中的体现

  • 以下代码是通过new创建一个Promise对象,我们需要传入一个回调函数,文章后续会称这个回调函数为executorresolverejectexecutor接收的两个回调函数
    let p1 = new Promise((resolve, reject) => {
        console.log("myPromise")
    })
    console.log(p1)

 

从这段代码我们可以看到executor函数体只打印了一句话,我们执行这段代码,结果如下,可以看到executor里面的代码被立即执行,同时p1处于初始状态pending 

image.png

  • 接着我们在executor里面调用resolve回调函数
    let p1 = new Promise((resolve, reject) => {
        console.log("myPromise")
        resolve()
    })
    console.log(p1)

在控制台查看打印结果如下吗,可以看到,此时p1的状态从pending变成fullfilled

image.png

  • 接着我们注释掉resolve,改为调用reject回调函数
    let p1 = new Promise((resolve, reject) => {
        console.log("myPromise")
        reject()
    })
    console.log(p1)

在控制台查看打印结果如下吗,可以看到,此时p1的状态从pending变成rejected
 

image.png


通过以上代码演示,可以知道当我们的executor没有调用任何回调函数之前,promise会处于pending状态,调用resolve之后状态变为fullfilled,即异步操作成功,调用reject之后状态变为rejected,也就是异步操作失败

🧐这时有人会问了,那如果我同时调用resolverejected呢,满足小朋友的好奇心,我们以代码演示一下👎

image.png

image.png

可以看到,当我们在executor同时调用两个回调函数时,不管哪个先调用,都只有一个会生效,从这个示例也可以得出一个结论,promise的状态只能从pending=>fullfilled或者从pending=>rejected

总结

Executor是在创建Promise时需要传入的一个回调函数,这个回调函数会被立即执行,并且传入两个参数,通常我们会在Executor中确定我们的Promise状态,通过resolve,可以兑现(fulfilledPromise的状态,我们也可以称之为已决议,通过reject,可以拒绝(rejectPromise的状态。

一旦状态被确定下来,Promise的状态会被锁死,该Promise的状态是不可更改的,在我们调用resolve的时候,如果resolve传入的值本身不是一个Promise(resolve传参的区别后续会讲到),那么会将该Promise的状态变成 兑现(fulfilled),在之后我们去调用reject时,已经不会有任何的响应了

三、Promise的使用

前文讲了那么多,到头来也没有说到promise怎么使用,状态改变之后发生什么,怎么把异步操作的结果与异步之后的程序相关联起来,别急😁,这一章会详细的解释怎么使用Promise

执行resolve或reject之后发生了什么

这里我们直接说下答案:当我们调用resolve回调函数时,会执行Promise对象的then方法传入的回调函数;当我们调用reject回调函数时,会执行promise对象的catch方法传入的回调函数。

then方法和catch方法是Promise原型上的一个方法:then方法会返回一个Promise,最多可以接收两个参数,分别是Promise成功和失败情况的回调函数;catch方法也返回一个Promise,当我们的Promise被拒绝时(reject)调用catch里的回调函数。

下面通过代码演示一下then方法和catch方法,注释掉reject(),打印的是‘失败的回调’,注释掉resolve(),打印的是‘成功的回调

    let p1 = new Promise((resolve, reject) => {
      reject()
      // resolve()
    })
    console.log(p1)
    p1.then(() => {
      console.log('成功的回调')
    }, () => {
      console.log("失败的回调")
    })

以上代码等价于

    let p1 = new Promise((resolve, reject) => {
      reject()
      // resolve()
    })
    console.log(p1)
    p1.then(() => {
      console.log('成功的回调')
    }).catch(() => {
      console.log("失败的回调")
    })
resolve回调函数参数的处理
  • 情况一:如果resolve传入一个普通的值或者对象,那么这个值会作为then回调的参数
    let p1 = new Promise((resolve, reject) => {
      resolve('情况一:普通值或对象')
    })
    p1.then((res) => {
      console.log(res) //情况一:普通值或对象
    })
    let p1 = new Promise((resolve, reject) => {
      resolve({obj:'情况一:普通值或对象'})
    })
    p1.then((res) => {
      console.log(res) //{obj: '情况一:普通值或对象'}
    })
  • 情况二:如果resolve中传入的是另外一个Promise,那么这个新Promise会决定原Promise的状态,并且新Promiseresolvereject的参数也会传给原来的promise
    const p = new Promise((resolve, reject) => {
      setTimeout(() => {
        resolve('我是新的promise')
      }, 1000)
    })
    const p1 = new Promise((resolve, reject) => {
      resolve(p)
    })
    p1.then((res) => {
      console.log(res) 
    })

在这段代码中,p是p1resolve中传入的Promise,因此,p1的状态是由p的状态决定的,由于p执行了resolve回调函数,因此状态是fullfilled,所以p1的状态也是fullfilled,运行之后控制台在1s后打印出“我是新的promise”

    const p = new Promise((resolve, reject) => {
      setTimeout(() => {
        reject('我是新的promise并且拒绝')
      }, 1000)
    })
    const p1 = new Promise((resolve, reject) => {
      resolve(p)
    })
    p1.then((res) => {
      console.log(”res:“+res)
    }).catch((err) => {
      console.log(“err:”+err)
    })

修改p回调的函数为reject之后,控制台打印“err:我是新的promise并且拒绝”,进一步验证以上结论

  • 情况三:resolve传入的是一个对象,并且该对象有实现then方法,那么会执行这个then方法,并且根据then方法的结果来决定Promise的状态
    const p = {
      name: 'javascript',
      then: function (resolve) {
        resolve("情况三")
      }
    }
    const p1 = new Promise((resolve, reject) => {
      resolve(p)
    })
    p1.then((res) => {
      console.log("res:" + res)
    }).catch((err) => {
      console.log("err:" + err)
    })

以上代码在控制台输出“res:情况三”

Promise的.then和.catch方法的调度

我们前面对then和catch方法的调用都只有一次,假如我们调用了多次呢🤔

    const p1 = new Promise((resolve, reject) => {
      resolve("成功的回调")
    })
    p1.then((res) => {
      console.log("res1:" + res)
    })
    p1.then((res) => {
      console.log("res2:" + res)
    })
    p1.then((res) => {
      console.log("res3:" + res)
    })

打开控制台会发现每个then里面的回调都被执行了,说明then是可以被多次调用的,每次调用我们都可以传入对应的fulfilled回调,当Promise的状态变成fulfilled的时候,这些回调函数都会被执行。

那么catch是否也会出现相同的结果呢,当我们在执行下面这段代码的时候,控制台会打印所有catch回调里面的内容,因此catch也是可以被多次调用的,每次调用我们都可以传入对应的reject回调;当Promise的状态变成reject的时候,这些回调函数都会被执行;

    const promise = new Promise((resolve, reject) => {
      reject("failure")
    })
    promise.then(res => {
      console.log("成功的回调:", res)
    }).catch(err => {
      console.log("失败的回调1:", err)
    })
    promise.catch(err => {
      console.log("失败的回调2:", err)
    })
    promise.catch(err => {
      console.log("失败的回调3:", err)
    })
    promise.catch(err => {
      console.log("失败的回调4:", err)
    })
    promise.catch(err => {
      console.log("失败的回调5:", err)
    })
then和catch方法的返回值

1、then

then方法本身是有返回值的,他的返回值是一个Promise,所以我们可以对其进行链式调用

    const p1 = new Promise((resolve, reject) => {
      resolve("成功的回调")
    })
    p1.then((res) => {
      console.log("res:" + res)
      return res + 'abc'
    }).then((res1) => {
      console.log("res1:" + res1)
    })

控制台会输出 res:成功的回调 res1:成功的回调abc

那么.then方法返回的这个Promise的状态是由什么决定的呢,事实上,这个新Promise的决议是等到then方法传入的回调函数有返回值时, 才会进行决议。

then方法中的回调函数在执行的时候,返回的promise处于pending状态,当返回一个结果时,会处于fullfilled状态,并且将结果作为resolve的参数,因此链式调用的then方法里的回调函数的参数是上一个then方法的返回值

下面我们来谈谈then方法返回值的几种情况吧😁

  • 情况一:返回一个普通的值a。将这个值a作为resolve的参数,因此在后面的.then方法里的回调函数获取到的参数就是a
    const p1 = new Promise((resolve, reject) => {
      resolve("成功的回调")
    })
    p1.then((res) => {
      return 'aaaaa'
    }).then((res1) => {
      console.log("res1:" + res1) //res1:aaaaa
      return 'bbbbb'
    }).then((res2) => {
      console.log("res2:" + res2) //res2:bbbbb
    })
  • 情况二:返回一个Promise。如果返回了一个PromiseA,那么then返回的PromiseB的状态会由PromiseA的状态决定,并且将PromiseA的状态的回调函数的参数作为PromiseB的状态的回调函数的参数
    const p = new Promise((resolve, reject) => {
      setTimeout(() => {
        resolve('ccccc')
      }, 1000)
    })
    const p1 = new Promise((resolve, reject) => {
      resolve("成功的回调")
    })
    p1.then((res) => {
      return p
    }).then((res1) => {
      console.log("res1:" + res1)
      return 'ddddd'
    }).then((res2) => {
      console.log("res2:" + res2)
    })

上面这段代码在1s后输出res1:ccccc,res2:ddddd

  • 情况三:返回一个thenable对象,如果then方法里面的回调函数返回了一个带有then方法的对象,那么then方法返回的PromiseAd的状态是由then方法里的结果决定的
   const promise = new Promise((resolve, reject) => {
      resolve("aaaaaaa")
      // reject()
    })
    promise.then(res => {
      return {
        then: function (resolve, reject) {
          resolve("thenable")
        }
      }
    }).then(res => {
      console.log("thenable test:", res) // thenable test: thenable
    })

2、catch

catch也会返回一个Promise,因此也是支持链式调用的,且catch后面可以调用then或者catch方法,我们先来看下面一段代码

    const p1 = new Promise((resolve, reject) => {
      reject("失败的回调")
    })
    p1.catch((res) => {
      console.log('catch里面的回调函数')
    }).then((res1) => {
      console.log("res1:" + res1)
    }).catch((res2) => {
      console.log("res2:" + res2)
    })

大家伙猜猜这段代码会打印出什么,是catch里面的回调函数 res2:undefined呢,还是catch里面的回调函数 res1:undefined,下面为大家揭晓答案

image.png

事实上,在p1.catch执行完后是会执行.then后面的回调而不是.catch后面的回调,这是因为为catch传入的回调在执行完后,默认状态依然会是fulfilled的;

如果我们想让.catch后面继续执行catch该怎么做呢❓答案是我们需要抛出一个异常,如下面代码所示

    const p1 = new Promise((resolve, reject) => {
      reject("失败的回调")
    })
    p1.catch((res) => {
      console.log('catch里面的回调函数')
      throw new Error('error message')
    }).then((res1) => {
      console.log("res1:" + res1)
    }).catch((res2) => {
      console.log("res2:" + res2)
    })

控制台会输出

image.png

综上:链式调用中的.catch方法的执行时机,是由上一个promise是否抛出异常决定的,如果上一个Promise照常返回一个值,执行的是链式调用中的then方法

Promise的finally

finally是ES9中新增的一个特性,无论promise变成fullfilled状态还是rejected状态,都会执行finally里面的回调,而且finally不接收任何参数,也用代码演示一遍

 let p1 = new Promise((resolve, reject) => {
      resolve('abc')
  })
  p1.then((res) => {
      console.log(res)
  }).catch((err) => {
      console.log(err)
  }).finally(() => {
      console.log('finally')
  })

上面代码的执行结果是abc finally

四、promise的类方法

前面我们讲过的方法都是属于Promise的实例方法,存放在Promiseprototype上,实际上,Promise也存在类方法,且在实际开发中用到的频率也很高,下面我们以此介绍一下Promise最主要的几个类方法

Promise.all

Promise.all的作用是将多个Promise包裹在一起形成一个新的Promise,并且这个新的Promise的状态是由包裹的Promise的状态共同决定的:

  • 当所有的Promise的状态变成fullfilled,新的Promise的状态变为fullfilled,并将所有promise的返回值组成一个数组
  • 当有一个Promise的状态变成reject,新的Promise的状态会变成reject,并且会将第一个rejectPromise的返回值作为参数
    let p1 = new Promise((resolve, reject) => {
        resolve('Promise')
    })
    let p2 = p1.then((res) => {
        return res + 'aaa'
    })
    let p3 = p2.then((res) => {
        return res + 'bbb'
    })
    Promise.all([p1, p2, p3]).then(res => {
        console.log(res) // ["Promise", "Promiseaaa", "Promiseaaabbb"]
    })
    let p1 = new Promise((resolve, reject) => {
        resolve('Promise')
    })
    let p2 = new Promise((resolve, reject) => {
        reject('reject状态的Promise')
    })
    Promise.all([p1, p2]).then((res) => {
        console.log('res:' + res)
    }).catch(err => {
        console.log('err:' + err)  //err:reject状态的Promise
    })
Promise.resolve

Promise.resolve(res)  方法返回一个以给定值解析后的 Promise对象,有时候我们已经有一个现成的值希望将其转换成Promise可以使用该类方法

  Promise.resolve('这是一个Promise')
  //等价于
  new Promise(resolve=>resolve('这是一个Promise'))

 

其中resolve方法中的参数的类型,同第三章中回调函数参数的处理一致。如果这个值是一个 promise,那么将返回这个 promise;如果这个值是 thenable(即带有 then方法),返回的 promise 会“跟随”这个 thenable的对象,采用它的最终状态;否则返回的 promise 将以此值完成

Promise.reject

reject方法类似于resolve方法,只是会将Promise对象的状态设置为reject状态

  Promise.reject('这是一个Promise')
  //等价于
  new Promise((resolve,reject)=>reject('这是一个Promise'))

Promise.reject传入的参数无论是什么形态,都会直接作为reject状态的参数传递到catch

Promise.race

如果有一个Promise有了结果,我们就希望决定最终新Promise的状态,那么可以使用race方法,如以下代码输出结果为‘err:Promise1’

  let p1 = new Promise((resolve, reject) => {
      reject('Promise1')
  })
  let p2 = new Promise((resolve, reject) => {
      reject('Promise2')
  })
  Promise.race([p1, p2]).then((res) => {
      console.log('res:' + res)
  }).catch(err => {
      console.log('err:' + err) //err:Promise1
  })
Promise.any

any方法是ES12中新增的方法,和race方法是类似的,不同的是any方法会等到一个fulfilled状态,才会决定新Promise的状态,如果所有的Promise都是reject的,那么也会等到所有的Promise都变成rejected状态。

如果所有的Promise都是reject的,那么会报一个AggregateError的错误

  let p1 = new Promise((resolve, reject) => {
      reject('Promise1')
  })
  let p2 = new Promise((resolve, reject) => {
      reject('Promise2')
  })
  Promise.any([p1, p2]).then((res) => {
      console.log('res:' + res)
  }).catch(err => {
      console.log(err)  //AggregateError: All promises were rejected
  })
Promise.allSettled

all方法有一个缺陷:当有其中一个Promise变成reject状态时,新Promise就会立即变成对应的reject状态。那么对于resolved的,以及依然处于pending状态的Promise,我们是获取不到对应的结果的

在ES11(ES2020)中,添加了新的API Promise.allSettled,该方法会在所有的Promise都有结果(settled),无论是fulfilled,还是rejected时,才会有最终的状态

并且这个Promise的结果一定是fulfilled的,以代码演示一下

    let p1 = new Promise((resolve, reject) => {
        reject('Promise1')
    })
    let p2 = new Promise((resolve, reject) => {
        reject('Promise2')
    })
    let p3 = new Promise((resolve, reject) => {
        resolve('Promise3')
    })
    Promise.allSettled([p1, p2, p3]).then((res) => {
        console.log(res)
    }).catch(err => {
        console.log(err)
    })

 

image.png

可以看出allSettled的结果是一个数组,数组中存放着每一个Promise的结果,并且是对应一个对象的;这个对象中包含status状态,以及对应的value

总结:

Promise是每个前端都需要弄清楚的一个知识点,在我们实际开发项目中也是大有用处的。本人以前初学的时候总以为自己学会了,但后面回顾的时候又总觉得自己没有学过这个东西一样,根本没有真正理解Promise的用处,最近在手写一个Promise的时候思路被卡住了,于是决定写一篇关于Promise的文章,让知识点刻在脑子里,再去实现Promise,边学边记边理解,效果比只看而不写好得多

这篇文章是本人的第一篇文章,本人也只是一个每天在公司写业务的小前端,肯定有很多不足的地方,有误的地方望大家批评指正,也是我继续写文章的动力👣


作者:https://juejin.cn/post/7240859916403015739
 

  • 12
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
LVS(Linux Virtual Server)是一种基于 Linux 系统的负载均衡集群技术,它主要用于将网络流量分发到多个服务器上,以提高系统的可靠性、可扩展性和性能。 LVS 集群一般包括四个组件:调度器(LVS 调度器)、前端服务器(负载均衡器)、后端服务器(真实服务器)和存储服务器(用于共享数据)。首先,调度器接收来自客户端的请求,然后根据配置的调度算法(如轮询、加权轮询、最小连接数等)将请求分发到多个前端服务器。前端服务器接收到请求后,通过相应的负载均衡算法将请求转发到后端的真实服务器上进行处理。在整个过程中,存储服务器用于存放共享的数据,以确保所有的真实服务器都能获取到相同的数据,并提供一致的服务。 LVS 集群的优点是能够提高网站的稳定性和可靠性,当某一台服务器出现故障时,调度器会自动将请求分发到其他可用的服务器上,从而保证服务的连续性。同时,LVS 集群还能够通过增加前端服务器和后端服务器的数量来提高系统的性能和吞吐量,以满足不断增长的用户需求。 在实际应用中,LVS 集群需要合理配置,包括选择合适的调度算法、调整每台服务器的权重、选择适当的硬件设备等。此外,还需要及时监控集群的运行状态,及时发现和解决故障,以确保整个系统的正常运行。 总的来说,LVS 负载均衡集群是一种强大而高效的集群技术,能够帮助企业提高系统的可靠性和性能,是现代互联网应用中不可或缺的重要组成部分。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值