前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >手写 Promise

手写 Promise

作者头像
EchoROne
发布于 2022-08-15 00:34:14
发布于 2022-08-15 00:34:14
38600
代码可运行
举报
文章被收录于专栏:玩转大前端玩转大前端
运行总次数:0
代码可运行

我们会通过手写一个符合 Promise/A+ 规范的 Promise来深入理解它,并且手写 Promise 也是一道大厂常考题,在进入正题之前,推荐各位阅读一下 【翻译】Promises/A+规范-图灵社区,这样才能更好地理解这个章节的代码。

实现一个简易版 Promise

在完成符合 Promise/A+ 规范的代码之前,我们可以先来实现一个简易版 Promise,因为在面试中,如果你能实现出一个简易版的 Promise基本可以过关了。

那么我们先来搭建构建函数的大体框架

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
const PENDING = 'pending'
const RESOLVED = 'resolved'
const REJECTED = 'rejected'

function MyPromise(fn) {
  const that = this
  that.state = PENDING
  that.value = null
  that.resolvedCallbacks = []
  that.rejectedCallbacks = []
  // 待完善 resolve 和 reject 函数
  // 待完善执行 fn 函数
}
  • 首先我们创建了三个常量用于表示状态,对于经常使用的一些值都应该通过常量来管理,便于开发及后期维护
  • 在函数体内部首先创建了常量 that,因为代码可能会异步执行,用于获取正确的 this对象
  • 一开始 Promise 的状态应该是 pending
  • value 变量用于保存 `resolve` 或者 reject 中传入的值
  • resolvedCallbacksrejectedCallbacks 用于保存 then 中的回调,因为当执行完 Promise 时状态可能还是等待中,这时候应该把 then 中的回调保存起来用于状态改变时使用

接下来我们来完善 resolvereject 函数,添加在 MyPromise 函数体内部

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
function resolve(value) {
  if (that.state === PENDING) {
    that.state = RESOLVED
    that.value = value
    that.resolvedCallbacks.map(cb => cb(that.value))
  }
}

function reject(value) {
  if (that.state === PENDING) {
    that.state = REJECTED
    that.value = value
    that.rejectedCallbacks.map(cb => cb(that.value))
  }
}

这两个函数代码类似,就一起解析了

  • 首先两个函数都得判断当前状态是否为等待中,因为规范规定只有等待态才可以改变状态
  • 将当前状态更改为对应状态,并且将传入的值赋值给 value
  • 遍历回调数组并执行

完成以上两个函数以后,我们就该实现如何执行 Promise 中传入的函数了

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
try {
  fn(resolve, reject)
} catch (e) {
  reject(e)
}
  • 实现很简单,执行传入的参数并且将之前两个函数当做参数传进去
  • 要注意的是,可能执行函数过程中会遇到错误,需要捕获错误并且执行 reject 函数

最后我们来实现较为复杂的 then 函数

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
MyPromise.prototype.then = function(onFulfilled, onRejected) {
  const that = this
  onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : v => v
  onRejected =
    typeof onRejected === 'function'
      ? onRejected
      : r => {
          throw r
        }
  if (that.state === PENDING) {
    that.resolvedCallbacks.push(onFulfilled)
    that.rejectedCallbacks.push(onRejected)
  }
  if (that.state === RESOLVED) {
    onFulfilled(that.value)
  }
  if (that.state === REJECTED) {
    onRejected(that.value)
  }
}
  • 首先判断两个参数是否为函数类型,因为这两个参数是可选参数
  • 当参数不是函数类型时,需要创建一个函数赋值给对应的参数,同时也实现了透传,比如如下代码
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// 该代码目前在简单版中会报错
// 只是作为一个透传的例子
Promise.resolve(4).then().then((value) => console.log(value))    
  • 接下来就是一系列判断状态的逻辑,当状态不是等待态时,就去执行相对应的函数。如果状态是等待态的话,就往回调函数中 `push` 函数,比如如下代码就会进入等待态的逻辑
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
new MyPromise((resolve, reject) => {
  setTimeout(() => {
    resolve(1)
   }, 0)
  }).then(value => {
  console.log(value)
})    

以上就是简单版 Promise 实现,接下来一小节是实现完整版 Promise 的解析,相信看完完整版的你,一定会对于 Promise 的理解更上一层楼。

实现一个符合 Promise/A+ 规范的 Promise

这小节代码需要大家配合规范阅读,因为大部分代码都是根据规范去实现的。

我们先来改造一下 resolvereject 函数

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
function resolve(value) {
  if (value instanceof MyPromise) {
    return value.then(resolve, reject)
  }
  setTimeout(() => {
    if (that.state === PENDING) {
      that.state = RESOLVED
      that.value = value
      that.resolvedCallbacks.map(cb => cb(that.value))
    }
  }, 0)
}
function reject(value) {
  setTimeout(() => {
    if (that.state === PENDING) {
      that.state = REJECTED
      that.value = value
      that.rejectedCallbacks.map(cb => cb(that.value))
    }
  }, 0)
}

* 对于 `resolve` 函数来说,首先需要判断传入的值是否为 `Promise` 类型

* 为了保证函数执行顺序,需要将两个函数体代码使用 `setTimeout` 包裹起来

接下来继续改造 then 函数中的代码,首先我们需要新增一个变量 promise2,因为每个 then 函数都需要返回一个新的 Promise 对象,该变量用于保存新的返回对象,然后我们先来改造判断等待态的逻辑

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
if (that.state === PENDING) {
  return (promise2 = new MyPromise((resolve, reject) => {
    that.resolvedCallbacks.push(() => {
      try {
        const x = onFulfilled(that.value)
        resolutionProcedure(promise2, x, resolve, reject)
      } catch (r) {
        reject(r)
      }
    })

    that.rejectedCallbacks.push(() => {
      try {
        const x = onRejected(that.value)
        resolutionProcedure(promise2, x, resolve, reject)
      } catch (r) {
        reject(r)
      }
    })
  }))
}
  • 首先我们返回了一个新的 Promise 对象,并在 Promise 中传入了一个函数
  • 函数的基本逻辑还是和之前一样,往回调数组中 push 函数
  • 同样,在执行函数的过程中可能会遇到错误,所以使用了 try...catch 包裹
  • 规范规定,执行 onFulfilled 或者 onRejected 函数时会返回一个 x,并且执行 Promise 解决过程,这是为了不同的 Promise 都可以兼容使用,比如 JQuery 的 Promise能兼容 ES6 的 Promise

接下来我们改造判断执行态的逻辑

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
if (that.state === RESOLVED) {
  return (promise2 = new MyPromise((resolve, reject) => {
    setTimeout(() => {
      try {
        const x = onFulfilled(that.value)
        resolutionProcedure(promise2, x, resolve, reject)
      } catch (reason) {
        reject(reason)
      }
    })
  }))
}
  • 其实大家可以发现这段代码和判断等待态的逻辑基本一致,无非是传入的函数的函数体需要异步执行,这也是规范规定的
  • 对于判断拒绝态的逻辑这里就不一一赘述了,留给大家自己完成这个作业

最后,当然也是最难的一部分,也就是实现兼容多种 PromiseresolutionProcedure 函数

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
function resolutionProcedure(promise2, x, resolve, reject) {
  if (promise2 === x) {
    return reject(new TypeError('Error'))
  }
}

首先规范规定了 x 不能与 promise2 相等,这样会发生循环引用的问题,比如如下代码

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
let p = new MyPromise((resolve, reject) => {
  resolve(1)
})
let p1 = p.then(value => {
  return p1
})

然后需要判断 x 的类型

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
if (x instanceof MyPromise) {
    x.then(function(value) {
        resolutionProcedure(promise2, value, resolve, reject)
    }, reject)
}

这里的代码是完全按照规范实现的。如果 xPromise 的话,需要判断以下几个情况:

  1. 如果 x 处于等待态,Promise 需保持为等待态直至 x 被执行或拒绝

2. 如果 x 处于其他状态,则用相同的值处理 Promise

当然以上这些是规范需要我们判断的情况,实际上我们不判断状态也是可行的。

接下来我们继续按照规范来实现剩余的代码

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
let called = false
if (x !== null && (typeof x === 'object' || typeof x === 'function')) {
  try {
    let then = x.then
    if (typeof then === 'function') {
      then.call(
        x,
        y => {
          if (called) return
          called = true
          resolutionProcedure(promise2, y, resolve, reject)
        },
        e => {
          if (called) return
          called = true
          reject(e)
        }
      )
    } else {
      resolve(x)
    }
  } catch (e) {
    if (called) return
    called = true
    reject(e)
  }
} else {
  resolve(x)
}
  • 首先创建一个变量 `called` 用于判断是否已经调用过函数
  • 然后判断 `x` 是否为对象或者函数,如果都不是的话,将 `x` 传入 `resolve` 中
  • 如果 `x` 是对象或者函数的话,先把 `x.then` 赋值给 `then`,然后判断 `then` 的类型,如果不是函数类型的话,就将 `x` 传入 `resolve` 中
  • 如果 `then` 是函数类型的话,就将 `x` 作为函数的作用域 `this` 调用之,并且传递两个回调函数作为参数,第一个参数叫做 `resolvePromise` ,第二个参数叫做 `rejectPromise`,两个回调函数都需要判断是否已经执行过函数,然后进行相应的逻辑
  • 以上代码在执行的过程中如果抛错了,将错误传入 `reject` 函数中

总结

以上就是符合 Promise/A+ 规范的实现了,如果你对于这部分代码尚有疑问,欢迎在评论中与我互动。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
Python 发送带附件的email
from email.MIMEText import MIMEText from email.MIMEMultipart import MIMEMultipart import smtplib
用户7999227
2021/11/01
7900
Nagios开发邮件报警程序
当前公司nagios已经正常使用,也能报警,但是邮件报警采用的是sendmail,发件人总是nagios@localhost,收到后经常被加入到邮件中的垃圾箱,并且有些邮箱服务器有反垃圾检测,导致用户接收不到邮件。为解决这个问题,决定自己写一个邮件发送程序。
星哥玩云
2022/07/01
5470
python接口自动化(三十二)--Python发送邮件(常见四种邮件内容)番外篇——上(详解)
  本篇文章与前边没有多大关联,就是对前边有关发邮件的总结和梳理。在写脚本时,放到后台运行,想知道执行情况,会通过邮件、SMS(短信)、飞信、微信等方式通知管理员,用的最多的是邮件。在linux下,Shell脚本发送邮件告警是件很简单的事,有现成的邮
北京-宏哥
2019/09/11
2.3K0
python接口自动化(三十二)--Python发送邮件(常见四种邮件内容)番外篇——上(详解)
zabbix通过python脚本发告警邮件
python脚本为敏捷开发脚本,在zabbix监控也起到重要作用,以下是使用python脚本发送告警邮件配置方法。
菲宇
2019/06/11
1.2K0
zabbix通过python脚本发告警邮件
Python3实现自动发送邮件
首先了解SMTP(简单邮件传输协议),邮件传送代理程序使用SMTP协议来发送电邮到接收者的邮件服务器。SMTP协议只能用来发送邮件,不能用来接收邮件,而大多数的邮件发送服务器都是使用SMTP协议。SMTP协议的默认TCP端口号是25。
用户9925864
2022/07/27
3420
Python3实现自动发送邮件
python发送邮件
1 # -*- coding: UTF-8 -*- 2 ''' 3 发送txt文本邮件 4 http://www.cnblogs.com/liu-ke 5 ''' 6 import smtplib 7 from email.mime.text import MIMEText 8 mailto_list=['***@**.***'] 9 mail_host="smtp.****.com" #设置服务器 10 mail_user="***@**.**" #用户名 11 mai
流柯
2018/08/30
6470
smtplib模块发送邮件
# 导入 smtplib 和 MIMEText import smtplib from email.mime.text import MIMEText
代码伴一生
2021/11/02
3180
Shell脚本里调用Python程序
脚本背景:主管要求看门狗程序不仅仅只是看门,还要在看门成功的时候发送邮件给各个开发人员,而且必须要用公司原有的python程序作为发送邮件的主程序,所以需要在原有的看门狗程序上加一句话,而这个看门狗程序恰恰是shell程序,两种不同程序混搭交织,还有变量的混搭交织,很是让人爱恨交织。
py3study
2020/01/09
1.6K0
python 发送邮件(文字、表格、附
import pandas as pd import smtplib   from email.mime.text import MIMEText   from email.mime.multipart import MIMEMultipart filename='C:\\Users\\thinkpad\\Desktop\\1.xlsx' #附件地址 def send_mail(to_list,sub,context,filename):  #to_list:收件人;sub:主题;content:邮件内容     mail_host="smtp.163.com"  #设置服务器     mail_user="XXXX@163.com"    #用户名     mail_pass="xxxxxx"   #口令      mail_postfix="163.com"  #发件箱的后缀     me="服务器"+"<"+mail_user+"@"+mail_postfix+">"   #这里的“服务器”可以任意设置,收到信后,将按照设置显示     msg = MIMEMultipart() #给定msg类型     msg['Subject'] = sub #邮件主题     msg['From'] = me     msg['To'] = ";".join(mailto_list)      msg.attach(context)     #构造附件1     att1 = MIMEText(open(filename, 'rb').read(), 'xls', 'gb2312')     att1["Content-Type"] = 'application/octet-stream'     att1["Content-Disposition"] = 'attachment;filename='+filename[-6:]#这里的filename可以任意写,写什么名字,邮件中显示什么名字,filename[-6:]指的是之前附件地址的后6位     msg.attach(att1)     try:           s = smtplib.SMTP()           s.connect(mail_host)  #连接smtp服务器         s.login(mail_user,mail_pass)  #登陆服务器         s.sendmail(me, mailto_list, msg.as_string())  #发送邮件         s.close()          return True       except Exception:             return False   if __name__ == '__main__':       mailto_list=["zhanghaili@autoht.com"]     a=pd.DataFrame({'数列1':(1,1,1,1),'数列2':(2,2,2,2),'数列3':(3,3,3,3),'数列4':(4,4,4,4)})     a.index={'行1','行2','行3','行4'} #这里dataframe类型a就是要输出的表格     sub="test"     d='' #表格内容     for i in range(len(a)):         d=d+"""         <tr>           <td>""" + str(a.index[i]) + """</td>           <td>""" + str(a.iloc[i][0]) + """</td>           <td width="60" align="center">""" + str(a.iloc[i][1]) + """</td>           <td width="75">""" + str(a.iloc[i][2]) + """</td>           <td width="80">""" + str(a.iloc[i][3]) + """</td>         </tr>"""     html = """\ <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <body> <div id="container"> <p><strong>测试程序邮件发送:</strong></p> <div id="content">  <table width="30%
py3study
2020/01/06
2.3K0
python发送邮件(二)——smtplib模块和email模块
一、模块介绍 1、smtplib 模块(用于邮件的发送) ①理论解释 smtplib.SMTP([host[, port[, local_hostname[, timeout]]]]) 通过这个语句,可以向SMTP服务器发送指令,执行相关操作(如:登陆、发送邮件)。所有的参数都是可选的。 host:smtp服务器主机名 port:smtp服务的端口,默认是25;端口号可以省略。 但是使用25号端口有一个问题,就是保密性不够好,数据都是明文传输,没有加密。 现在一般都推荐使用SSL,Secure So
Elsa_阿尼
2021/07/27
5.4K0
python发送邮件(二)——smtplib模块和email模块
python3实现邮件的发送
使用的email和smtplib模块,这里简单介绍下smtplib.SMTP()类
dogfei
2020/07/31
3810
【编程技巧】使用Python发送HTML格式的邮件
脚本示例: import smtplib from email.mime.text import MIMEText mailto_list=["YYY@YYY.com"] mail_host="smtp.XXX.com" #设置服务器 mail_user="XXX" #用户名 mail_pass="XXXX" #口令 mail_postfix="XXX.com" #发件箱的后缀 def send_mail(to_list,sub,content): #to_list:收件人;sub:主题;
Luga Lee
2022/03/25
1.1K0
python模块:smtplib模块
格式(1):smtpObj=smtplib.SMTP([host [,port [,local_hostname]]])
py3study
2020/01/10
1.6K0
Linux两种发邮件的方式
1、下载安装msmtp wget https://marlam.de/msmtp/releases/msmtp-1.6.7.tar.xz tar -xvJf msmtp-1.6.7.tar.xz ls cd msmtp-1.6.7 ./configure --prefix=/usr/local/msmtp make && make install 2、配置msmtp账号 cd /usr/local/msmtp mkdir etc #配置文件目录和配置文件都要自己建 cd etc 手动创建配置文件vi msmtprc # Set default values for all following accounts. defaults logfile /usr/local/msmtp/msmtp.log # The SMTP server of the provider. account default #你的发送邮件服务器 host smtp.126.com port 25 #要从哪个邮箱发出 from xxxx@126.com #这里如果使用on的话会报 "msmtp: cannot use a secure authentication method"错误 auth login tls off #邮箱用户名 user xxxx@126.com #邮箱用户名 #邮箱密码,这里可是明文的,如果你觉得不安全可以把文件改为600属性 password xxxxxxx # Set a default account account default: test Esc,shift+: 进入命令模式,输入x,保存退出。 由于password是明码,所以我们需要修改此文件的权限 chmod 600 etc/msmtprc 3、测试 /usr/local/msmtp/bin/msmtp youremail@test.com 输入任意字符,然后按Ctrl+D退出,查看邮件是否收到。 由于设置了日志,可以到 /usr/local/msmtp/msmtp.log,查看日志,发信成功失败都会有记录。 按ctrl+d结束 但是它不能够发送附件,所以安装mutt 4、安装mutt vi /etc/Muttrc ,编辑mutt的总设置,修改以下几行 set from="发送邮件地址" set sendmail="/usr/local/msmtp/bin/msmtp" set use_from=yes set realname="发件人" set editor="vi" 发件地址最好与msmtp设置的账号相同,否则可能会出错。 5、测试一下mutt是否有效 echo "测试测试" | mutt -s "测试" 测试邮件地址 echo "testmail" |mutt -s "test" [-a /etc/hosts] test@163.com 这里的-a 是指添加附件,如果是多个附件的话就 多加几个 -a 文件名 可以使用mutt进行邮件备份等工作了,结合cron使用,可以实现的功能相当多。
菲宇
2019/06/11
1.6K0
【测试开发】python系列教程:smtplib库
SMTP(Simple Mail Transfer Protocol)即简单邮件传输协议,它是一组用于由源地址到目的地址传送邮件的规则,由它来控制信件的中转方式。
雷子
2023/08/21
2960
【测试开发】python系列教程:smtplib库
Python发送邮件
SMTP(Simple Mail Transfer Protocol)即简单邮件传输协议,它是一组用于由源地址到目的地址传送邮件的规则,由它来控制信件的中转方式。
青阳
2021/11/26
9180
通过python为ZABBIX发告警邮件
把 以上代码保存为  send.py  放到 /etc/zabbix/alertscripts 目录下
py3study
2020/01/06
5230
Python发邮件
代码 1、mail.py #-*-coding:utf-8-*- #!/bin/pyton import sys import smtplib import logging from email.mime.text import MIMEText def send_mail(to_list, cc_list, html, sub): me = mail_user msg = MIMEText(html, _subtype='html', _charset='utf-8') # 格式化
week
2020/05/12
1.9K0
python发送邮件案例分析
1、运用for循环,实现群发功能 接收方的昵称是统一的一个,可再优化一下,实现更加个性化,更加自由的发送邮件 from email.header import Header #处理邮件主题 from email.mime.text import MIMEText # 处理邮件内容 from email.utils import parseaddr, formataddr #用于构造特定格式的收发邮件地址 import smtplib #用于发送邮件 # 函数小工具 def _format_addr(s):
Elsa_阿尼
2021/07/28
8010
Python进阶37-smtp及Django发邮件
-多年互联网运维工作经验,曾负责过大规模集群架构自动化运维管理工作。 -擅长Web集群架构与自动化运维,曾负责国内某大型金融公司运维工作。 -devops项目经理兼DBA。 -开发过一套自动化运维平台(功能如下): 1)整合了各个公有云API,自主创建云主机。 2)ELK自动化收集日志功能。 3)Saltstack自动化运维统一配置管理工具。 4)Git、Jenkins自动化代码上线及自动化测试平台。 5)堡垒机,连接Linux、Windows平台及日志审计。 6)SQL执行及审批流程。 7)慢查询日志分析web界面。
DriverZeng
2022/09/26
7980
相关推荐
Python 发送带附件的email
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验