深入理解Promise之一步步教你手写Promise构造函数

news2024/11/28 1:30:30

目录

  • 前言
  • 一,手写教学
    • 1.1 基本结构
    • 1.2 resolve与reject结构搭建
    • 1.3 resolve与reject代码实现
    • 1.4 throw抛出异常改变状态
    • 1.5 promise对象状态只能转换一次
    • 1.6 then方法进行回调
    • 1.7 异步任务的回调执行
    • 1.8 执行多个回调的实现
    • 1.9 同步修改状态then方法结果返回
    • 1.10 异步修改状态then方法结果返回
  • 二,总体代码
  • 后记

前言

Promise是前端面试中笔记走重要的一个考点。如何真正的了解promise?我们不如自己动手,写一个可以拿来用的Promise.js。

下面是循序递进的写法教学,本文的最后也有完整写法的代码可以借鉴,希望能够帮助大家!

后面如果有机会会写一篇详解promise的博客,欢迎关注!

一,手写教学

1.1 基本结构

想要写一个最初的结构,我们不妨先用一下Promise,然后再进行分析。

        let p = new Promise((resolve, reject) => {
            resolve('巧克力小猫猿')
        })

        p.then(value => {
            console.log(value)
        }, reason => {
            console.log(reason)
        })

如上代码,我们了解到了,Promise构造函数里面传进去的是一个函数,所以我们也为其加一个函数类型的形参:

function Promise(executor) {
}

上述代码也用到了then方法,但目前我们的结构中还没有then,所以也写一个then,且有两个参数:

Promise.prototype.then = function(onResolved, onRejected) {
}

以上,基本结构已经写完。

1.2 resolve与reject结构搭建

我们都知道,在使用Promise的时候,内部会传一个执行器(函数),且该函数有两个函数类型的参数:resolve和reject。

因此,我们在写结构的时候也要声明着两个函数,并在内部执行器调用的时候使用它们,代码如下:

function Promise(executor) {
    //同步调用
    function resolve(data) {
    }

    function reject(data) {
    }
    executor(resolve, reject)
}

//先添加一个then
Promise.prototype.then = function(onResolved, onRejected) {
}

1.3 resolve与reject代码实现

我们先来复习一下,promise对象有两个属性,一个是promiseState,意为promise的状态,默认值为pedding,成功状态为resolved,失败状态为rejected;还有一个是promiseResult,意为promise返回的结果。这些变量既然是内置属性,我们应该先对其进行声明:

    //声明属性,状态promiseState和结果值promiseResult
    this.PromiseState = 'pedding'
    this.promiseResult = null

接着,resolve与reject有两个作用:改变promise状态,返回结果。所以我们可以在resolve与reject两个函数中去操作promise的状态与结果:

    //保存实例对象中的this
    const self = this
    function resolve(data) {
        //修改状态(promiseState),设置对象结果值(promiseResult)
        self.promiseState = 'resolved'
        self.promiseResult = data
    }

    function reject(data) {
        self.promiseState = 'rejected'
        self.promiseResult = data
    }
    executor(resolve, reject)

这里为什么用的不是this是self?普通函数中的this都指向window,而这里我们修改的是构造函数中的属性,而不是window的。所以我们把构造函数中的this赋值给了self。注意,构造函数中的this指向的是构造函数

1.4 throw抛出异常改变状态

在promise的使用中,一旦使用throw,则状态为失败,且返回值为throw中传入的。

那么,我们也要在构造函数内部写一个捕获异常的代码,利用try,catch。这里如果有疑问可以去搜索下try,catch的相关博客:

    try{
        executor(resolve, reject)
    }catch(e) {
        reject(e)
    }

1.5 promise对象状态只能转换一次

在使用promise时,promise的状态只能转换一次:由pedding转换为resolved,或者由pedding转换为rejected。

所以本节我们来做这个限制:只需要在resolve与reject函数中加一段代码:

        if (self.promiseState !== 'pedding') return

意思是:状态只能是由pedding改变为resolved与rejected的,不能由其他状态变为resolved与rejected。

1.6 then方法进行回调

首先我们来分析一下then的用法,看如下代码:

        let p = new Promise((resolve, reject) => {
            resolve('OK')
        })

        p.then(value => {
            console.log(value)
        }, reason => {
            console.log(reason)
        })

这一段代码,先使用resolve,并在里面传值,告诉promise,状态promiseState为resolved,传入的值promiseResult为ok。接着调用then,如果状态是成功的则调用第一个回调,失败则调用第二个。所以回调函数的调用需要判断条件,判断是成功还是失败,接着,value和reason是promiseResult,所以这两个回调的参数我们也要加上。

以上是思路,代码如下:

Promise.prototype.then = function (onResolved, onRejected) {
    // 调用回调函数
    if(this.promiseState === 'resolved') {
        onResolved(this.promiseResult)
    }
    if(this.promiseState === 'rejected') {
        onRejected(this.promiseResult)
    }
}

1.7 异步任务的回调执行

如果没有书写相关代码,异步编程无法实现,如下代码:

        let p = new Promise((resolve, reject) => {
            setTimeout(() => {
            	resole('OK')
            }, 1000)
        })

        p.then(value => {
            console.log(value)
        }, reason => {
            console.log(reason)
        })

由于这里有一个异步任务,所以最先执行的是p.then,而此时状态还未改变(pedding状态),所以无法输出,因此,我们需要做一些处理。

做什么处理呢:先思考,我们在什么时候使用then?两种情况,一种是上面同步的任务执行完毕,代码按顺序调用then;另一种就是,上面由异步任务,所以跳过先调用then。我们限制考虑的就是第二种情况,如果是这种情况的话,promise的状态还没有来的及改变(pedding),所以没办法执行代码。

我们希望的是在异步任务完成后再调用代码。那么如何实现这一点?如果调用then时状态为pedding,我们可以先声明一份当前的回调函数在当前的构造函数中:

    //声明一个属性保存回调函数
    this.callback = {}

接着,在then中对callback进行保存:

    //判断pedding的状态
    if (this.promiseState === 'pedding') {
        //保存回调函数
        this.callback = {
            onResolved: onResolved,
            onRejected: onRejected
        }
    }

在resolve函数中,等待状态改变后再调用then,也就是调用callback中对应的onResolved:

    function resolve(data) {
        //修改状态(promiseState),设置对象结果值(promiseResult)
        //限制更改
        if (self.promiseState !== 'pedding') return
        self.promiseState = 'resolved'
        self.promiseResult = data

        //调用then
        if (self.callback.onResolved) {
            self.callback.onResolved(data)
        }
    }

同理,rejecte函数中也一样:

    function reject(data) {
        if (self.promiseState !== 'pedding') return
        self.promiseState = 'rejected'
        self.promiseResult = data

        //调用then
        if (self.callback.onRejected) {
            self.callback.onRejected(data)
        }
    }

1.8 执行多个回调的实现

promise有一个特点,就是,promise可以串联多个操作任务。promise的then会返回一个新的promise,也就是then的链式调用。通过then的链式调用串联多个同步的异步任务。

目前我们的Promise没办法实现该功能,如果写多个then,前面的只会被覆盖。所以本节要解决的就是这个问题。

解决方法:把callback设置成一个数组,一旦是链式调用则在数组里添加内容:

    this.callbacks = []
    if (this.promiseState === 'pedding') {
        //保存回调函数
        this.callbacks.push({
            onResolved: onResolved,
            onRejected: onRejected
        })
    }

调用方式也需要更改:

        self.callbacks.forEach(item => {
            item.onResolved(data)
        }) 
        self.callbacks.forEach(item => {
            item.onRejected()
        })

1.9 同步修改状态then方法结果返回

首先我们来回顾一下then方法的返回规律:then方法结果的返回在于回调函数的返回值,如果返回一个非promise的值,则结果是一个字符串;如果是一个promise类型的值,返回的是该promise的结果与状态。

这里我们做一步操作:then返回的是一个promise对象,接我们对该对象的返回值类型进行判断,如果返回的是一个promise对象,则另该promise对象调用then,一直调用到结果不是一个promise对象为止;如果结果不是promise对象则令其调用resolve:

Promise.prototype.then = function (onResolved, onRejected) {
    return new Promise((resolve, rejecte) => {
        if (this.promiseState === 'resolved') {
            let result = onResolved(this.promiseResult)
            //如果result是一个promise的对象
            if(result instanceof Promise) {
                result.then(v => {
                    resolve(v);
                }, r=> {
                    reject(r)
                })
            }else {
                resolve(result)
            }
        }
        //此处省略了下面的代码

如果要抛出异常,状态会改变为失败:

    return new Promise((resolve, rejecte) => {
        if (this.promiseState === 'resolved') {
            try{
                let result = onResolved(this.promiseResult)
                //如果result是一个promise的对象
                if(result instanceof Promise) {
                    result.then(v => {
                        resolve(v);
                    }, r=> {
                        reject(r)
                    })
                }else {
                    resolve(result)
                }
            }catch(e) {
                reject(e)
            }
        }

这一块大家理解可能会有点困难,其实本质上是一个递归,一直在调用,直到返回值不再是promise类型的为止。

1.10 异步修改状态then方法结果返回

如下代码,在使用promise时,参杂异步任务:

        let p = new Promise((resolve, reject) => {
            setTimeout(() => {
                resolve('OK')
            }, 1000)
        })

        const res = p.then(value => {
            console.log(value)
        }, reason => {
            console.log(reason)
        })

        console.log(res)

利用我们的的代码,还没能来得及实现状态的改变和返回值的改变:
在这里插入图片描述
本节将要解决的就是这个问题。我们的代码对于pedding的判断有一个分支:

        //判断pedding的状态
        if (this.promiseState === 'pedding') {
            //保存回调函数
            this.callbacks.push({
                onResolved: onResolved,
                onRejected: onRejected
            })
        }

对于异步任务,如果遇到先调用then的情况,1.7中给过解释:等待状态改变后立刻调用callback内部的函数。

        //判断pedding的状态
        if (this.promiseState === 'pedding') {
            //保存回调函数
            this.callbacks.push({
                onResolved: function() {
                    onResolved(self.PromiseResult)
                },
                onRejected: function() {
                    onRejected(self.PromiseResult)
                }
            })
        }
        if (this.promiseState === 'pedding') {
            //保存回调函数
            this.callbacks.push({
                onResolved: function() {
                    let result = onResolved(self.PromiseResult)
                    if(result instanceof Promise) {
                        result.then(v => {
                            resolve(v);
                        }, r=> {
                            reject(r)
                        })
                    }else {
                        resolve(result)
                    }
                },

二,总体代码

以上,把Promise基本上可以实现。还有一些拓展的一些方法还未补充。有兴趣的读者朋友可以自己下来研究。
以下是整体代码:

function Promise(executor) {
    //声明属性,状态promiseState和结果值promiseResult
    this.promiseState = 'pedding'
    this.promiseResult = null
    //声明一个属性保存回调函数
    this.callbacks = []

    //保存实例对象中的this
    const self = this
    function resolve(data) {
        //修改状态(promiseState),设置对象结果值(promiseResult)
        //限制更改
        if (self.promiseState !== 'pedding') return
        self.promiseState = 'resolved'
        self.promiseResult = data

        //调用then
        self.callbacks.forEach(item => {
            item.onResolved(data)
        })   
    }

    function reject(data) {
        if (self.promiseState !== 'pedding') return
        self.promiseState = 'rejected'
        self.promiseResult = data

        //调用then
        self.callbacks.forEach(item => {
            item.onRejected()
        })
    }

    //异常处理
    try {
        executor(resolve, reject)
    } catch (e) {
        reject(e)
    }
}

//先添加一个then
Promise.prototype.then = function (onResolved, onRejected) {
    const self = this
    // 调用回调函数
    //返回值是一个promise对象
    return new Promise((resolve, rejecte) => {
        if (this.promiseState === 'resolved') {
            try{
                let result = onResolved(this.promiseResult)
                //如果result是一个promise的对象
                if(result instanceof Promise) {
                    result.then(v => {
                        resolve(v);
                    }, r=> {
                        reject(r)
                    })
                }else {
                    resolve(result)
                }
            }catch(e) {
                reject(e)
            }
        }
        if (this.promiseState === 'rejected') {
            try{
                let result = onRejected(this.promiseResult)
                //如果result是一个promise的对象
                if(result instanceof Promise) {
                    result.then(v => {
                        resolve(v);
                    }, r=> {
                        reject(r)
                    })
                }else {
                    resolve(result)
                }
            }catch(e) {
                reject(e)
            }
        }
        
        //判断pedding的状态
        if (this.promiseState === 'pedding') {
            //保存回调函数
            this.callbacks.push({
                onResolved: function() {
                    let result = onResolved(self.PromiseResult)
                    if(result instanceof Promise) {
                        result.then(v => {
                            resolve(v);
                        }, r=> {
                            reject(r)
                        })
                    }else {
                        resolve(result)
                    }
                },
                onRejected: function() {
                    let result = onRejected(self.PromiseResult)
                    if(result instanceof Promise) {
                        result.then(v => {
                            resolve(v);
                        }, r=> {
                            reject(r)
                        })
                    }else {
                        resolve(result)
                    }
                }
            })
        }
    })

}

后记

感谢阅读,后续有机会会出一篇关于Promise的博文,如果感兴趣可以先关注下,谢谢!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/180396.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【手写 Promise 源码】第四篇 - 翻译并理解 Promise A+ 规范

一,前言 上一篇,根据对 Promise 的分析和了解,实现了一个简版 Promise,主要涉及以下内容: Promise 的实现思路;Promise A 规范(简版);Promise 简版实现和功能测试&…

KVM虚拟化之小型虚拟机kvmtool的使用记录

根据 kvmtool github仓库文档的描述,类似于QEMU,kvmtool是一个承载KVM Guest OS的 host os用户态虚拟机,作为一个纯的完全虚拟化的工具,它不需要修改guest os即可运行, 不过,由于KVM基于CPU的硬件虚拟化支持&#xff0…

读《哲学的故事》

文章目录读《哲学的故事》🚩 遇见🌻 简述🌾 部分摘抄读《哲学的故事》 一本书读过后,我有种脑子里又被塞进了很多新东西的感觉,也有种想要自我抒发、宣泄的欲望。可真到要说的时候,又好像无话可说。总归勉…

Java转换流(InputStreamReader/OutputStreamWriter)

文章目录概述为什么会有转换流?InputStreamReaderOutputStreamWriter概述 转换流是字节流到字符流的桥梁,在转换的过程中,可以指定编码。转换流也是一种处理流,它提供了字节流和字符流之间的转换。 转换流的两个类 InputStreamR…

1.设计模式的前奏

哪些维度评判代码质量的好坏? 常用的评价标准 可维护性(maintainability):维护代码的成本可读性(readability)可扩展性(extensibility):码应对未来需求变化的能力灵活性&#xff0…

【keepass】密码管理软件-推荐插件和相关工具合集-keepass工作流分析(自动填充、美化界面、快速添加记录、安全增强、软件和数据库维护类)

Keepass有很多已经开源的插件,生态良好,在官网有专门的插件推荐区。安装插件的方法很简单,直接把下载好的插件文件放在plugins文件夹内,重启软件即可。下面我以几大功能推荐一些keepass插件或搭配使用的浏览器扩展,以求…

Coolify系列-手把手教学解决局域网局域网中的其他主机访问虚拟机以及docker服务

背景 我在windows电脑安装了一个VM,使用VM开启了Linux服务器,运行docker,下载服务镜像,然后运行服务,然后遇到了主机无法访问服务的问题。 问题排查 STEP1:首先要开启防火墙端口,这个我的Coolify系列其他文章有详细…

【c++】设置控制台窗口字体颜色和背景色(system和SetConsoleTextAttribute函数 )(内含超好玩的c++游戏链接)

目录 游戏推荐 研究初步 SetConsoleTextAttribute函数 原型 参数 举个栗子 最后 题外话 一篇游戏笔记。。。 游戏推荐 最近,在玩(完)一个c的控制台游戏。 啊,真的非常好玩。虽然是一个文字游戏,但有很多隐…

分享137个ASP源码,总有一款适合您

ASP源码 分享137个ASP源码,总有一款适合您 下面是文件的名字,我放了一些图片,文章里不是所有的图主要是放不下..., 137个ASP源码下载链接:https://pan.baidu.com/s/13nF0yADJhSBonIFUIoymPQ?pwdmsl8 提取码&#x…

【C++】位图、布隆过滤器概念与模拟实现

目录 一、位图 1.1 位图的概念 1.2 位图的使用 1.3 位图的实现 1.4 位图的应用 二、布隆过滤器 2.1 布隆过滤器 2.2 布隆过滤器的实现 2.3 布隆过滤器练习题 一、位图 1.1 位图的概念 所谓位图,就是用每一位来存放某种状态,适用于海量数据&am…

监控Python 内存使用情况和代码执行时间

我的代码的哪些部分运行时间最长、内存最多?我怎样才能找到需要改进的地方?” 在开发过程中,我很确定我们大多数人都会想知道这一点,而且通常情况下存在开发空间。在本文中总结了一些方法来监控 Python 代码的时间和内存使用情况…

【24】C语言 | 调试技巧

目录 1、调试概念: 2、Debug和Release的介绍 3、windows中的快捷键 4、案例一:求1! 2!3!...n! 5、案例二:下面的代码输出什么? 6、案例三:实现一个strcopy的函数 …

零入门容器云网络实战-3->Underlay网络与Overlay网络总结

本篇文章主要用于收集、整理、总结关于Underlay网络以及overlay网络相关知识点。 1、underlay网络介绍? 1.1、什么是underlay网络? Underlay网络就是: 传统IT基础设施网络,由交换机和路由器等设备组成,借助以太网协议…

3分钟搭建起聊天机器人需要的NoneBot2环境

创建nonebot2运行环境 官网上说这里的Python版本要高于3.8.0,还会有其他的依赖。 所以这里推荐大家使用虚拟环境,Poetry、venv、Conda,我这里用的是conda环境(不同的项目依赖可能有所不同,所以才创建虚拟环境&#xf…

[羊城杯 2020]EasySer

目录 信息搜集 代码审计 参数扫描 信息搜集 先扫下目录 .htaccess&#xff1b;robots.txt&#xff1b;flag.php&#xff1b;index.php 在robots.txt下看到了/star1.php 进入star1.php发现出现ser.php <!-- 小胖说用个不安全的协议从我家才能进ser.php呢&#xff01; !--…

蓝桥杯刷题015——最少刷题数(二分法+前缀和)

问题描述 小蓝老师教的编程课有 N 名学生, 编号依次是 1…N 。第 i 号学生这学期刷题的数量是 Ai​ 。 对于每一名学生, 请你计算他至少还要再刷多少道题, 才能使得全班刷题比他多的学生数不超过刷题比他少的学生数。 输入格式 第一行包含一个正整数 N 。 第二行包含 N 个整数:…

学成在线项目开发技巧整理---第一部分

学成在线项目开发技巧整理---第一部分1.数据字典2.http-client远程测试插件,可以保存测试数据3.三种跨域解决4.具有多层级数据查询思路5.Mybaits分页插件原理6.根据文件后缀解析出mime-type7.大文件上传8.Spring事务什么时候会失效9.分布式文件系统MinIo10.构建独立文件系统11.…

3.3Sram和Dram

文章目录一、引子二、存储元件1.DRAM芯片&#xff08;1&#xff09;栅极电容1&#xff09;存储2&#xff09;读出&#xff08;2&#xff09;物理特性&#xff08;3&#xff09;DRAM刷新&#xff08;4&#xff09;DRAM地址线复用2.SRAM芯片&#xff08;1&#xff09;双稳态触发器…

爬虫之JS的解析

JS的解析 学习目标&#xff1a; 了解 定位js的方法了解 添加断点观察js的执行过程的方法应用 js2py获取js的方法 1 确定js的位置 对于前面人人网的案例&#xff0c;我们知道了url地址中有部分参数&#xff0c;但是参数是如何生成的呢&#xff1f; 毫无疑问&#xff0c;参数肯…

gin全解

文章目录介绍安装快速开始&#xff08;三种启动方式&#xff09;参数获取querystring参数其他不常用方法表单参数&#xff08;form参数&#xff09;其他不常用方法获取path参数参数绑定文件上传单个文件多个文件请求&#xff08;ctx.Request)响应gin.H{}字符串响应JSON/YAML/TO…