js手写Promise(下)

news2024/11/14 15:18:41

目录

  • resolve与reject的调用时机
    • 封装优化
  • 回调返回Promise
    • isPromise
    • 手动调用then
  • 微队列
  • catch
  • resolve
  • reject
  • all
    • 传入的序列为空
    • 传入的值非Promise
  • race
  • 完整的Promise代码

如果没有看过上半部分的铁铁可以看看这篇文章
js手写Promise(上)

resolve与reject的调用时机

Promisethen中,我们需要解决两个问题,一个是onFulfilled和onRejected什么时候调用,一个是resolve和reject什么时候调用,第一个问题我们在上篇文章中解决了,现在我们需要解决第二个问题
具体而言,什么时候调用resolvereject分为两种情况

  1. 传入的参数不是函数
    因为我们已经提前将resolvereject传递到了handlers中,所以我们可以在run中处理相关逻辑

    #run() {
    	if (this.#state === MyPromise.#PENDING) return
     	while (this.#handlers.length > 0) {
        	const handler = this.#handlers.shift()
        	if (this.#state === MyPromise.#FULFILLED) {
            	if (typeof handler.onFulfilled !== "function") {
                	handler.resolve(this.#value)
            	} else {
                	handler.onFulfilled(this.#value)
            	}
        	}
        	else if (this.#state === MyPromise.#REJECTED) {
            	if (typeof handler.onRejected !== "function") {
                	handler.reject(this.#value)
            	} else {
                	handler.onRejected(this.#value)
            	}
        	}
    	}
    }
    

    如果传入的参数不是函数的话,那then返回的Promise穿透了,状态与调用then的Promise实例状态一致,如果调用then的实例状态为fulfilledthen返回的实例就调用resolve,反之亦然

  2. 传入的参数是函数
    如果传入的参数是函数,我们就需要判断在执行函数的时候有没有报错,如果没有就代表函数执行成功,调用resolve,否则调用reject

    #run() {
    if (this.#state === MyPromise.#PENDING) return
    while (this.#handlers.length > 0) {
        const handler = this.#handlers.shift()
        if (this.#state === MyPromise.#FULFILLED) {
            if (typeof handler.onFulfilled !== "function") {
                handler.resolve(this.#value)
            } else {
                try {
                    const data = handler.onFulfilled(this.#value)
                    handler.resolve(data)
                } catch (error) {
                    handler.reject(error)
                }
            }
        }
        else if (this.#state === MyPromise.#REJECTED) {
            if (typeof handler.onRejected !== "function") {
                handler.reject(this.#value)
            } else {
                try {
                    const data = handler.onRejected(this.#value)
                    handler.resolve(data)
                } catch (error) {
                    handler.reject(error)
                }
            }
        }
    }
    }
    

封装优化

这样问题就解决了,但是我们发现函数中有许多重复代码,我们可以将这些代码封装成一个函数

#run() {
    if (this.#state === MyPromise.#PENDING) return
    while (this.#handlers.length > 0) {
        const handler = this.#handlers.shift()
        if (this.#state === MyPromise.#FULFILLED) {
            this.#runOne(handler.onFulfilled, handler.resolve, handler.reject)
        }
        else if (this.#state === MyPromise.#REJECTED) {
            this.#runOne(handler.onRejected, handler.resolve, handler.reject)
        }
    }
}
#runOne(callback, resolve, reject) {
    if (typeof callback !== "function") {
        const settled = this.#state === MyPromise.#FULFILLED ? resolve : reject
        settled(this.#value)
    } else {
        try {
            const data = callback(this.#value)
            resolve(data)
        } catch (error) {
            reject(error)
        }
    }
}

回调返回Promise

这种情况比较特殊,如果返回的结果是一个Promise的话调用resolve还是reject由这个新的Promise实例决定,我们只需要手动调用它的then方法

isPromise

在调用它的then方法之前我们还需要判断返回的结果是不是一个PromisePromiseA+规范规定,对象或是函数,如果存在then方法就是Promise,所以我们可以写出这么一个辅助函数

class MyPromise {
    static #isPromise(promise) {
        if (typeof promise === "function" || typeof promise === "object") {
            if (typeof promise.then === "function") return true
        }
        return false
    }
}

手动调用then

现在我们就可以手动调用then方法了

#runOne(callback, resolve, reject) {
    if (typeof callback !== "function") {
        const settled = this.#state === MyPromise.#FULFILLED ? resolve : reject
        settled(this.#value)
    } else {
        try {
            const data = callback(this.#value)
            if (MyPromise.#isPromise(data)) data.then(resolve, reject)
            else resolve(data)
        } catch (error) {
            reject(error)
        }
    }
}

微队列

至此我们的then方式实现的差不多了,还有一个小细节就是,then方法里的任务是放入微任务队列里执行的,所以我们还需要封装一个函数用于将任务放入微任务队列

#runOne(callback, resolve, reject) {
    MyPromise.#mircoTask(() => {
        if (typeof callback !== "function") {
            const settled = this.#state === MyPromise.#FULFILLED ? resolve : reject
            settled(this.#value)
        } else {
            try {
                const data = callback(this.#value)
                if (MyPromise.#isPromise(data)) data.then(resolve, reject)
                else resolve(data)
            } catch (error) {
                reject(error)
            }
        }
    })
}
static #mircoTask(callback) {
    if (typeof window === "object" && MutationObserver) {
        const observer = new MutationObserver(callback)
        const p = document.createElement('p')
        observer.observe(p, { childList: true })
        p.innerText = '1'
    } else if (typeof global === "object" && process) {
        process.nextTick(callback)
    } else {
        setTimeout(callback, 0)
    }
}

如果想要把一个任务放入微任务队列需要根据环境分类处理,具体来说有以下几种情况

  1. node环境
    node环境里有一个api叫processprocess.nextTick能将一个任务放入微任务队列中
  2. 浏览器环境
    浏览器中有一个观察器叫MutationObserver,它用于观察一个元素是否变化,如果变化了就将预先设定好的函数放入微任务队列
  3. 其他环境
    如果宿主环境既不是node也不是浏览器,或者不支持MutationObserverprocess,那么就只能通过setTimeOut来模拟微队列了

最后我们来测试一下我们的then方法

let p1 = new MyPromise((resolve, reject) => {
    reject(123)
})
p1.then((res) => {
    console.log("1resolve" + res)
}, (err) => {
    console.log("1reject" + err)
})
p1.then((res) => {
    console.log("2resolve" + res)
}, (err) => {
    return new MyPromise((resolve, reject) => {
        resolve(err)
    })
}).then((res) => {
    console.log("3resolve" + res)
}, (err) => {
    console.log("3reject" + err)
})

结果

catch

catch的实现与then类似,都是向handlers里放入回调,只不过catch放入的回调中onFulfilledundefined

class MyPromise {
    catch(onRejected) {
        return new MyPromise((resolve, reject) => {
            this.#handlersPush(undefined, onRejected, resolve, reject)
            this.#run()
        })
    }
}

我们来测试一下

let p1 = new MyPromise((resolve, reject) => {
    reject(123)
})
p1.catch((err) => {
    console.log(err)
})
let p2 = new MyPromise((resolve, reject) => {
    reject(456)
})
p2.then(null, (err) => {
    return new MyPromise((resolve, reject) => {
        reject(789)
    })
}).catch(err => {
    console.log(err)
})

结果

resolve

这里我们要实现的resolvePromise类方法,回忆我们之前使用Promise的经验,如果resolve中传递的不是Promise,那么Promise会将其包装成一个Promise返回,如果传入的是一个Promise,那么会调用它的then方法,知道了这些我们的代码就可以这么写

static resolve(value) {
    return new MyPromise((resolve, reject) => {
        if (MyPromise.#isPromise(value)) value.then(resolve, reject)
        else resolve(value)
    })
}

我们来测试一下

MyPromise.resolve(1).then(console.log)
MyPromise.resolve(new MyPromise((resolve, reject) => {
    setTimeout(() => {
        resolve(2)
    }, 1000)
})).then(console.log)

结果

reject

rejectresolve类似,只不过resolve是调用resolve方法,而reject是调用reject方法

static reject(reason) {
    return new MyPromise((resolve, reject) => {
        if (MyPromise.#isPromise(reason)) reason.then(resolve, reject)
        else reject(reason)
    })
}

因为和resolve类似,所以我就不测试了

all

all也是Promise的一个类方法,我们需要向all里传入一个参数,表示为一系列Promise的序列,可以是set,也可以是数组all也是返回一个Promise,知道了这些后我们就能将all的声明写出来

static all(promises) {
    return new MyPromise((resolve, reject) => {
        
    })
}

那么现在的问题就是我们什么时候调用resolvereject

传入的序列为空

如果传入的序列为空就没什么好说的了,直接resolve([])就行,那怎么判断序列是否为空呢,定义一个长度变量,我们可以通过for...of来遍历序列,每遍历一次长度变量自增,遍历完后如果长度变量依旧为0表示序列为空

static all(promises) {
    return new MyPromise((resolve, reject) => {
        let i = 0
        for (const item of promises) {
            i++
        }
        if (i === 0) resolve([])
    })
}

传入的值非Promise

因为我们并不确定拿到的东西是否是一个Promise,所以我们需要使用Promise.resolve将它包裹起来

static all(promises) {
    return new MyPromise((resolve, reject) => {
        let i = 0
        for (const item of promises) {
            i++
            MyPromise.resolve(item).then()
        }
        if (i === 0) resolve([])
    })
}

根据Promiseall的逻辑,如果序列中有一个失败,那all返回的Promise的状态就是失败
如果当前Promise的结果为成功的话则需要做两件事,一件事汇总结果,一件事判断Promise是否全部完成
因为all要求返回的结果与传递的序列顺序要求一致,所以在汇总结果时不能使用push,而是应该使用下标
我们每完成一个Promise就记一次数,只要这个数字和我们之前统计的长度变量相同,就代表着这一串Promise执行结束,可以resolve
所以我们的代码可以写成这个样子

static all(promises) {
    return new MyPromise((resolve, reject) => {
        let i = 0
        let result = []
        let fulfilled = 0
        for (const item of promises) {
            let index = i
            i++
            MyPromise.resolve(item).then((data) => {
                result[index] = data
                fulfilled++
                if (fulfilled === i) resolve(result)
            }, reject)
        }
        if (i === 0) resolve([])
    })
}

我们来测试一下

let p1 = new MyPromise((resolve, reject) => {
    resolve(1)
})
let p2 = new MyPromise((resolve, reject) => {
    reject(2)
})
MyPromise.all([]).then(console.log)
MyPromise.all([p1, p2]).then(console.log).catch(err => {
    console.log("err" + err)
})
MyPromise.all([p1, 2, 3, 4]).then(console.log)

结果

race

raceall都是Promise的类方法,实现思路也是大同小异,all是等待全部Promise的结果,race是只要有一个Promise有结果就行,代码如下

static race(promises) {
    return new MyPromise((resolve, reject) => {
        let i = 0
        for (const item of promises) {
            i++
            Promise.resolve(item).then(resolve, reject)
        }
        if (i === 0) resolve([])
    })
}

完整的Promise代码

class MyPromise {
    #state = "pending"
    #value = null
    static #PENDING = "pending"
    static #FULFILLED = "fulfilled"
    static #REJECTED = "rejected"
    #handlers = []
    constructor(executor) {
        const resolve = (data) => {
            this.#changeState(MyPromise.#FULFILLED, data)
        }
        const reject = (reason) => {
            this.#changeState(MyPromise.#REJECTED, reason)
        }
        try {
            executor(resolve, reject)
        } catch (error) {
            reject(error)
        }
    }
    then(onFulfilled, onRejected) {
        return new MyPromise((resolve, reject) => {
            this.#handlersPush(onFulfilled, onRejected, resolve, reject)
            this.#run()
        })
    }
    catch(onRejected) {
        return new MyPromise((resolve, reject) => {
            this.#handlersPush(undefined, onRejected, resolve, reject)
            this.#run()
        })
    }
    static resolve(value) {
        return new MyPromise((resolve, reject) => {
            if (MyPromise.#isPromise(value)) value.then(resolve, reject)
            else resolve(value)
        })
    }
    static reject(reason) {
        return new MyPromise((resolve, reject) => {
            if (MyPromise.#isPromise(reason)) reason.then(resolve, reject)
            else reject(reason)
        })
    }
    static all(promises) {
        return new MyPromise((resolve, reject) => {
            let i = 0
            let result = []
            let fulfilled = 0
            for (const item of promises) {
                let index = i
                i++
                MyPromise.resolve(item).then((data) => {
                    result[index] = data
                    fulfilled++
                    if (fulfilled === i) resolve(result)
                }, reject)
            }
            if (i === 0) resolve([])
        })
    }
    static race(promises) {
        return new MyPromise((resolve, reject) => {
            let i = 0
            for (const item of promises) {
                i++
                Promise.resolve(item).then(resolve, reject)
            }
            if (i === 0) resolve([])
        })
    }
    static #mircoTask(callback) {
        if (typeof window === "object" && MutationObserver) {
            const observer = new MutationObserver(callback)
            const p = document.createElement('p')
            observer.observe(p, { childList: true })
            p.innerText = '1'
        } else if (typeof global === "object" && process) {
            process.nextTick(callback)
        } else {
            setTimeout(callback, 0)
        }
    }
    static #isPromise(promise) {
        if (typeof promise === "function" || typeof promise === "object") {
            if (typeof promise.then === "function") return true
        }
        return false
    }
    #changeState(state, value) {
        if (this.#state !== MyPromise.#PENDING) return
        this.#state = state
        this.#value = value
        this.#run()
    }
    #handlersPush(onFulfilled, onRejected, resolve, reject) {
        this.#handlers.push({
            onFulfilled,
            onRejected,
            resolve,
            reject
        })
    }
    #run() {
        if (this.#state === MyPromise.#PENDING) return
        while (this.#handlers.length > 0) {
            const handler = this.#handlers.shift()
            if (this.#state === MyPromise.#FULFILLED) {
                this.#runOne(handler.onFulfilled, handler.resolve, handler.reject)
            }
            else if (this.#state === MyPromise.#REJECTED) {
                this.#runOne(handler.onRejected, handler.resolve, handler.reject)
            }
        }
    }
    #runOne(callback, resolve, reject) {
        MyPromise.#mircoTask(() => {
            if (typeof callback !== "function") {
                const settled = this.#state === MyPromise.#FULFILLED ? resolve : reject
                settled(this.#value)
            } else {
                try {
                    const data = callback(this.#value)
                    if (MyPromise.#isPromise(data)) data.then(resolve, reject)
                    else resolve(data)
                } catch (error) {
                    reject(error)
                }
            }
        })
    }
}

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

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

相关文章

FastAdmin西陆招聘SAAS

应用介绍 基于ThinkPHP和微信小程序开发的招聘平台系统,包含微信小程序求职者端、微信小程序企业招聘端、PC企业招聘端、PC管理平台端 招聘系统是一种利用计算机和网络技术建立起来的具有完整的信息管理、分析和查询功能的人才招聘平台。它主要用于企业、单位、机构…

Python爬虫http基本原理#2

Python爬虫逆向系列(更新中):http://t.csdnimg.cn/5gvI3 HTTP 基本原理 在本节中,我们会详细了解 HTTP 的基本原理,了解在浏览器中敲入 URL 到获取网页内容之间发生了什么。了解了这些内容,有助于我们进一…

pytorch入门第一天

今天作为入门pytorch的第一天。打算记录每天学习pytorch的一些理解和笔记,以用来后面回顾。当然如果能帮到和我一样的初学者,那也是不胜荣幸。作为一名初学者,难免有些地方会现错误,欢迎各位大佬指出 预备知识 这里主要介绍pyto…

从0开始学Docker ---Docker安装教程

Docker安装教程 本安装教程参考Docker官方文档,地址如下: https://docs.docker.com/engine/install/centos/ 1.卸载旧版 首先如果系统中已经存在旧的Docker,则先卸载: yum remove docker \docker-client \docker-client-latest…

MySQL篇之索引

一、定义 索引(index)是帮助MySQL高效获取数据的数据结构(有序)。在数据之外,数据库系统还维护着满足特定查找算法的数据结构(B树),这些数据结构以某种方式引用(指向)数据&#xff0…

Python爬虫requests库详解#3

使用 requests 上一节中,我们了解了 urllib 的基本用法,但是其中确实有不方便的地方,比如处理网页验证和 Cookies 时,需要写 Opener 和 Handler 来处理。为了更加方便地实现这些操作,就有了更为强大的库 requests&…

Cisco firepower2100系列使用FDM管理FTD

Cisco firepower2100系列使用FDM管理FTD 啥是FDM Firepower Device Manager 当思科Firepower系列运行的FTD镜像时,可以通过2种方式进行管理 第1种方式: FMC (Firepower management Center) 可以进行统一管理,一台FMC可以管理多个FTD&…

深度学习入门笔记(八)可以不断思考的模型:RNN与LSTM

8.1 循环神经网络RNN 之前学到的 CNN 和全连接,模型的输入数据之间是没有关联的,比如图像分类,每次输入的图片与图片之间就没有任何关系,上一张图片的内容不会影响到下一张图片的结果。但在自然语言处理领域,这就成了…

【vue3学习笔记】shallowReactive与shallowRef;readOnly与shallowReadOnly;toRaw与markRaw

尚硅谷Vue2.0Vue3.0全套教程丨vuejs从入门到精通 课程 P158节 《shallowReactive与shallowRef》笔记: reactive()与shallowReactive():reactive()处理后的数据是响应式的,对象内嵌套的深层结构全部是响应式的。shallowReactive()处理后的数据…

蓝桥杯---分小组

9名运动员参加比赛,需要分3组进行预赛. 有哪些分组的方案呢? 我们标记运动员为 A,B,C .... I 下面的程序列出了所有的分组方法。 该程序的正常输出为:

Guava RateLimiter单机实战指南

欢迎来到我的博客,代码的世界里,每一行都是一个故事 Guava RateLimiter单机实战指南 前言maven坐标引入业务实现重要参数和方法关于warmupPeriod实战 前言 想象一下你是一位大厨,正在烹饪美味佳肴。突然之间,前来就餐的人潮如潮水…

C语言辨析——声明int a[3][6], a[0][9]越界吗?

本文来源&#xff1a;声明int a[3][6], a[0][9]越界吗&#xff1f; 1. 问题 看下面的程序&#xff1a; #include <stdio.h> int main(void) {int a[3][6];for(int i0; i<3; i) {for(int j0; j<6; j){a[i][j] i * 6 j;}}printf("%d\n",a[0][9]);retu…

Uniapp(uni-app)学习与快速上手教程

Uniapp&#xff08;uni-app&#xff09;学习与快速上手教程 1. 简介 Uniapp是一个跨平台的前端框架&#xff0c;允许您使用Vue.js语法开发小程序、H5、安卓和iOS应用。下面是快速上手的步骤。 2. 创建项目 2.1 可视化界面创建 1、打开 HBuilderX&#xff0c;这是一款专为uni…

【服务器数据恢复】服务器RAID模块硬件损坏的数据恢复案例

服务器数据恢复环境&故障&#xff1a; 某品牌服务器中有一组由数块SAS硬盘组建的RAID5磁盘阵列&#xff0c;服务器操作系统是WINDOWS SERVER&#xff0c;服务器中存放企业数据&#xff0c;无数据库文件。 服务器出故障之前出现过几次意外断电的情况&#xff0c;服务器断电…

计算机网络基本知识(二)

文章目录 概要分层为什么分层怎么分层&#xff1f;1.实体2.协议3.服务 分层基本原则正式认识分层详细例子解释 总结 概要 分层知识&#xff1a;概念理解 分层 为什么分层 大致以上五点 为了解决上面的问题&#xff08;复杂&#xff09; 大问题划分为小问题 怎么分层&#…

Lua 教程

Lua 教程 (今天又又又开新坑啦) Lua 教程 手册简介 Lua 是一种轻量小巧的脚本语言&#xff0c;用标准C语言编写并以源代码形式开放。 手册说明 Lua是什么? Lua 是一个小巧的脚本语言。是巴西里约热内卢天主教大学&#xff08;Pontifical Catholic University of Rio de …

RPA财务机器人之UiPath实战 - 自动化操作Excel进行财务数据汇总与分析之流程建立与数据读取、处理、汇总、分析

一、案例介绍&#xff1a; A公司共有13个开在不同银行的帐户&#xff0c;分别用于不同的业务分部或地区分部收付款。公司总部为了核算每月的收支情况&#xff0c;查看银行在哪个月交易量频繁&#xff0c;需要每月汇总各个银行的帐户借方和贷方金额&#xff0c;并将其净收支&am…

人大金仓bat文件备份数据库

1&#xff09;使用环境变量来存储密码 setx KINGBASE_PASSWORD "abc123" 2&#xff09;编写backup.bat :: 设置备份参数 set "sys_dumpc:\Program Files\Kingbase\ES\V8\KESRealPro\V008R006C008B0014\ClientTools\bin\sys_dump" set "hostloca…

【经验】SPICE仿真 - Bob Pease会说No吗?

每一个读过我博客的人都知道&#xff0c;我使用SPICE模型仿真电路。你可能听说过Bob Pease&#xff0c;在SPICE领域相当执有己见&#xff0c;他曾经说过&#xff1a;“SPCIE模型削弱了你对所发生事物的洞察能力。SPICE模型实际上降低了你对电路如何工作的理解能力”。今天&…

创建本地yum源并安装tree命令(openEuler-20.03-LTS-SP3)

步骤 1&#xff1a;下载ISO镜像 首先&#xff0c;您需要从提供的URL下载ISO镜像文件&#xff1a; cd /opt wget https://mirrors.dotsrc.org/openeuler/openEuler-20.03-LTS-SP3/ISO/x86_64/openEuler-20.03-LTS-SP3-x86_64-dvd.iso步骤 2&#xff1a;挂载ISO镜像 接下来&am…