news 2026/4/17 17:49:29

前端高频面试题之手写Promise

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
前端高频面试题之手写Promise

1、什么是 promise?

Promise译为 “承诺”,是 JavaScript 中用于处理异步操作的解决方案。它代表一个异步操作的最终完成(或失败)及其结果值。

2、Promise 的特点

特点一:Promise 有三种状态。

  • pending:等待态,promise的初始状态
  • fulfilled:成功态,promise调用resolve函数后即会从pending等待态变为fulfilled成功态
  • rejected:失败态:promise调用reject函数后即会从pending等待态变为rejected失败态

特点二:Promise 状态不可逆。

  • promise的状态一旦发生变更,便无法再更改。比如调用resolvepending变为fulfilled,它的状态就永远是fulfilled了,再调用reject也无法从fulfilled变成rejected
  • 并且状态只能从pending变为fulfilledrejected,不能从fulfilledrejected返回到pending,这个也很好理解,状态只能前进不能倒退。

特点三:支持链式调用。

  • 通过.then().catch().finally()等方法进行链式调用。

3、Promise 手写流程

先看用法:

constp=newPromise((resolve,reject)=>{resolve(111);})p.then((value)=>{console.log(value)},(error)=>{console.log(error)})

首先,Promise肯定是一个类,所以我们才可以new它,然后Promise实例化的时候给它传入一个回调我们叫它executor方法,Promise内部会立即调用这个executor方法,并且会传入resolvereject两个函数作为调用参数,另外在Promise类的原型上应该提供一个then方法,它里面可以传入两个回调,分别为Promise成功的回调Promise失败的回调。调用resolve后会走入成功的回调中,调用reject后会走入失败的回调中

3.1 版本一:搭建整体架子

constPENDING='pending'constFULFILLED='fulfilled'constREJECTED='rejected'classPromise{constructor(executor){this.value=undefinedthis.reason=undefinedthis.status=PENDINGconstresolve=(value)=>{if(this.status===PENDING){this.value=valuethis.status=FULFILLED}}constreject=(reason)=>{if(this.status===PENDING){this.reason=reasonthis.status=REJECTED}}executor(resolve,reject);}then(onFulfilled,onRejected){if(this.status===FULFILLED){onFulfilled&&onFulfilled(this.value)}if(this.status===REJECTED){onRejected&&onRejected(this.reason)}}}module.exports=Promise;

3.2 版本二:支持异步的 resolve 或者 reject

如果是异步调用 resolve 或者 reject ,那么上面onFulfilledonRejected将无法执行了。

constp=newPromise((resolve,reject)=>{setTimeout(()=>{resolve(111);},1000)})p.then((value)=>{console.log(value)},(error)=>{console.log(error)})

所以我们需用两个数组充当队列把then里边的回调存起来。

classPromise{constructor(executor){// ...// 定义两个数组this.onResolvedCallbacks=[];this.onRejectedCallbacks=[];constresolve=(value)=>{if(this.status===PENDING){this.value=valuethis.status=FULFILLEDthis.onResolvedCallbacks.forEach(fn=>fn())}}constreject=(reason)=>{if(this.status===PENDING){this.reason=reasonthis.status=REJECTEDthis.onRejectedCallbacks.forEach(fn=>fn())}}// 默认执行executor函数,并传入resolve和reject函数executor(resolve,reject)}then(onFulfilled,onRejected){if(this.status===FULFILLED){onFulfilled&&onFulfilled(this.value)}if(this.status===REJECTED){onRejected&&onRejected(this.reason)}if(this.status===PENDING){this.onResolvedCallbacks.push(()=>{onFulfilled(this.value)})this.onRejectedCallbacks.push(()=>{onRejected(this.reason)})}}}

这里定义了两个数组onResolvedCallbacksonRejectedCallbacks分别存储 then 里面成功的回调失败的回调,然后再调用resolvereject时分别循环执行这两个数组里存储的回调函数。

3.3 版本三:支持 Promise 链式调用

比如:下面这段代码:

constp=newPromise((resolve,reject)=>{setTimeout(()=>{resolve(111)},1000)})p.then((value1)=>{console.log('value1',value1)return222},(error1)=>{console.log('error1',error1)}).then((value2)=>{console.log('value2',value2)},(error2)=>{console.log('error2',error2)})

它的打印结果为:

这个是如何实现的呢?

这个其实也简单,它内部调用then方法时,返回了一个新的promise,并让这个新的promise接管了它下一个then方法。

注意:这里不能返回this,这样会导致多个then方法全部受同一个promise控制。

classPromise{// ...then(onFulfilled,onRejected){constpromise2=newPromise((resolve,reject)=>{if(this.status===FULFILLED){// onFulfilled方法可能返回值或者promiseconstx=onFulfilled(this.value)resolvePromise(promise2,x,resolve,reject)}if(this.status===REJECTED){// onRejected方法可能返回值或者promiseconstx=onRejected(this.reason)resolvePromise(promise2,x,resolve,reject)}if(this.status===PENDING){this.onResolvedCallbacks.push(()=>{constx=onFulfilled(this.value)resolvePromise(promise2,x,resolve,reject)})this.onRejectedCallbacks.push(()=>{constx=onRejected(this.reason)resolvePromise(promise2,x,resolve,reject)})}})returnpromise2}}

最核心的就是resolvePromise,来看下它做了什么:

functionresolvePromise(promise2,x,resolve,reject){if(promise2===x){returnreject(newTypeError('UnhandledPromiseRejectionWarning: TypeError: Chaining cycle detected for promise #<Promise>'))}letcalled// 判断x的类型 x是对象或函数才有可能是一个promiseif(typeofx==='object'&&x!==null||typeofx==='function'){try{constthen=x.thenif(typeofthen==='function'){// 只能认为它是一个promisethen.call(x,(y)=>{if(called)returncalled=trueresolvePromise(promise2,y,resolve,reject)},(r)=>{if(called)returncalled=truereject(r)})}else{resolve(x)}}catch(e){if(called)returncalled=truereject(e)}}else{resolve(x)}}
  1. 首先,先判断新返回的一个promisepromise2是不是等于x,抛出错误UnhandledPromiseRejectionWarning: TypeError: Chaining cycle detected for promise #<Promise>,这一步是防止内部的循环引用。
  2. 声明一个变量called,相当于加了一把锁,让promise只能调用一次成功或者失败回调,防止死循环。
  3. 解析x,如果它的类型是object并且不为null,或者它是一个函数,并且它有then方法,我们认为这是一个promise
  4. 递归解析,then里面再次调用resolvePromise

3.4 版本四:模拟异步微任务

因为promiseEventLoop里面是个微任务,不过我们可以简单通过setTimeout模拟。

然后我们再加上一些报错的捕获代码以及一些参数的兼容代码,以及实现catch方法。

classPromise{constructor(executor){// ...// 这里增加try catchtry{executor(resolve,reject)}catch(e){reject(e)}}then(onFulfilled,onRejected){// 这里兼容下 onFulfilled 和 onRejected 的传参onFulfilled=typeofonFulfilled==='function'?onFulfilled:v=>v onRejected=typeofonRejected==='function'?onRejected:err=>{throwerr}constpromise2=newPromise((resolve,reject)=>{if(this.status===FULFILLED){// 用 setTimeout 模拟异步setTimeout(()=>{try{constx=onFulfilled(this.value)resolvePromise(promise2,x,resolve,reject)}catch(e){reject(e)}},0)}if(this.status===REJECTED){// 用 setTimeout 模拟异步setTimeout(()=>{try{constx=onRejected(this.reason)resolvePromise(promise2,x,resolve,reject)}catch(e){reject(e)}},0)}if(this.status===PENDING){this.onResolvedCallbacks.push(()=>{// 用 setTimeout 模拟异步setTimeout(()=>{try{constx=onFulfilled(this.value)resolvePromise(promise2,x,resolve,reject)}catch(e){reject(e)}},0)})this.onRejectedCallbacks.push(()=>{// 用 setTimeout 模拟异步setTimeout(()=>{try{constx=onRejected(this.reason)resolvePromise(promise2,x,resolve,reject)}catch(e){reject(e)}},0)})}})returnpromise2}// catch函数实际上里面就是调用了then方法catch(errCallback){returnthis.then(null,errCallback)}}
  1. executor执行时增加try catch,防止执行用户传入的函数直接就报错了,这时我们应该直接rejectpromise。
  2. 调用onFulfilledonRejected时,需要包裹setTimeout
  3. catch函数实际上里面就是调用了then方法,然后第一个参数传null

ok,这样就写的差不多了。最后我们来测试下我们写的promise是否符合规范。

4、测试 promise,使其符合 Promises/A+ 规范

promise是有规范的,即Promises/A+,我们可以跑一段脚本测试写的promise是否符合规范。

首先,需要在我们的promise增加如下代码:

// 测试脚本Promise.defer=Promise.deferred=function(){letdfd={}dfd.promise=newPromise((resolve,reject)=>{dfd.resolve=resolve dfd.reject=reject})returndfd}

然后安装promises-aplus-tests包,比如用npm可以使用命令npm install -g promises-aplus-tests安装到全局,然后使用命令promises-aplus-tests 文件名即可进行测试,里面有872测试用例,全部通过即可以认为这是一个标准的promise

测试全部通过,大功告成了!

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/16 10:51:04

【数据结构】单链表

目录 引言 什么是单链表 基本概念 核心特点&#xff1a; 单链表图解 单链表的实现 1.手动创建链表 测试结果 2.单链表结构 链表打印 创建新结点 尾插 时间复杂度O&#xff08;N&#xff09; 尾插测试 头插 时间复杂度O&#xff08;1&#xff09; 头插测试 尾删 …

作者头像 李华
网站建设 2026/4/18 3:24:55

Obsidian Ink 插件终极指南:5分钟掌握手写笔记革命性功能

Obsidian Ink 插件终极指南&#xff1a;5分钟掌握手写笔记革命性功能 【免费下载链接】obsidian_ink 项目地址: https://gitcode.com/gh_mirrors/ob/obsidian_ink 快速入门体验&#xff1a;从零开始的手写笔记之旅 Obsidian Ink 是一款专为 Obsidian 笔记软件设计的革…

作者头像 李华
网站建设 2026/4/17 6:59:30

Java学习日记——DAY7

今天学习了与Java异常处理相关的知识&#xff0c;汇总如下&#xff1a;1.用try{}catch&#xff08;&#xff09;{}finally{}的语法来处理异常&#xff0c;try里面还可以嵌套try和catch&#xff1b;2.try{}后面可搭配多个catch来处理不同的异常&#xff0c;同时可通过catch&…

作者头像 李华
网站建设 2026/4/18 3:25:52

基于Java的springboot/SSM+vue.js+uniapp小程序的非遗茶百戏科普小程序附带文章源码部署视频讲解等

文章目录前言详细视频演示具体实现截图核心技术介绍后端框架SpringBoot前端框架Vue持久层框架MyBaits为什么选择我代码参考数据库参考测试用例参考源码获取前言 &#x1f31e;博主介绍&#xff1a;✌CSDN特邀作者、资深全栈开发程序员&#xff0c;曾在互联网大厂担任高级职位、…

作者头像 李华
网站建设 2026/4/18 3:28:14

UE5 材质-21:

&#xff08;91&#xff09;这篇开始&#xff0c;跟着 B 站&#xff0c;游启明老师&#xff0c;再学一遍材质 黑白图&#xff0c; UE 里黑色是 0&#xff0c;白色是 1 &#xff1a; 启动引用查看器 &#xff1a; 让材质多使用引擎里的公共资源&#xff0c;易于迁移文件&…

作者头像 李华
网站建设 2026/4/17 3:40:02

Flutter 全场景开发实战宝典:组件化架构、性能优化与跨端适配深度解析

引言在移动应用开发领域&#xff0c;“多端一致体验”与“高效开发迭代”始终是开发者追求的核心目标。Flutter 作为 Google 推出的跨端开发框架&#xff0c;凭借“自绘 UI 引擎、单一代码库多端部署、原生级性能”三大核心优势&#xff0c;彻底打破了传统跨端方案“体验打折、…

作者头像 李华