JavaScript中,如何去手写一个promise

前言

Promise 是异步编程的一种解决方案,比传统的解决方案------回调函数和事件------更合理和更强大。它由社区最早提出和实现,ES6 将其写进了语言标准,统一了用法,原生提供了Promise对象。

Promise对象代表一个异步操作,有三种状态:pending(进行中)、fulfilled(已成功)和rejected(已失败)。只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态。这也是Promise这个名字的由来,它的英语意思就是"承诺",表示其他手段无法改变。

手写Promise

JavaScript 中,类是一个非常实用的一个方法,今天我们就使用它来简单封装一个 Promise。ES6 中类的使用这里就不多介绍了。

一、创建一个 PromiseA 的类。

首先我们需要创建这个类,如下:

kotlin 复制代码
class PromiseA {
    constructor(executor) {
        this.value = null   // 储存成功的值
        this.reason = null  // 储存失败的值
        this.state = 'pending'  // 储存状态值
        const onResolve = (value) => {
            if (this.state == 'pending') {
                this.value = value       // 改变成功的值
                this.state = 'fulfilled' // 改变状态值
            }
        }
        const onReject = (err) => {
            if (this.state == 'pending') {
                this.reason = err       // 改变失败的值
                this.state = 'rejected' // 改变状态值
            }
        }
        executor(onResolve, onReject)
    }
    then(onResolve, onReject) {
        if (this.state == 'fulfilled') {
            onResolve(this.value) // 调用成功的函数
        }
        if (this.state == 'rejected') {
            onReject(this.reason) // 调用失败的函数
        }
    }
}

以上代码中,我们简单的创建了一个 PromiseA 的类,其中 onResolve 是成功的函数,onReject 是失败的函数。value、reason 分别是成功和失败的值。state 则是 Promise 的状态值。

两个函数中之所以需要判断 state 的值是因为 Promise 的特性:状态凝固,之前的文章中有提到过。

二、处理 Promise 的三种状态。

上面的代码测试的时候会发现,同步的简单处理是没有问题的,但它主要的功能"处理异步"是无法使用的,接下来我们需要处理一下 pending(进行中)状态的问题。代码如下:

kotlin 复制代码
class PromiseA {
    constructor(executor) {
        this.value = null   
        this.reason = null  
        this.state = 'pending'  
        this.onResolveBack = [] // 储存成功的函数
        this.onRejectBack = []  // 储存失败的函数
        const onResolve = (value) => {
            if (this.state == 'pending') {
                this.value = value       
                this.state = 'fulfilled' 
                this.onResolveBack.forEach(fn => fn()) // 调用成功的函数
            }
        }
        const onReject = (err) => {
            if (this.state == 'pending') {
                this.reason = err      
                this.state = 'rejected' 
                this.onRejectBack.forEach(fn => fn()) // 调用失败的函数
            }
        }
        executor(onResolve, onReject)
    }
    then(onResolve, onReject) {
        if (this.state == 'fulfilled') {
            onResolve(this.value) 
        }
        if (this.state == 'rejected') {
            onReject(this.reason) 
        }
        if (this.state == 'pending') {
            // 储存成功的函数
            this.onResolveBack.push(() => {
                onResolve(this.value)
            })
            // 储存失败的函数
            this.onRejectBack.push(() => {
                onReject(this.reason)
            })
        }
    }
}

以上代码中,我们又添加了两个数组用来存储成功或者失败的函数。需要注意的是,我们存储的时候需要在外包一层函数,用来传递我们的成功或者失败的值。

如果是异步的话,PromiseA 的状态是进行中的,就直接存储到我们的数组中,等待异步执行的时候,使用 forEach 方法循环执行我们的函数。

三、解决链式调用和处理循环调用

以上,我们基本完成了 Promise 的手写,接下来需要处理一下链式调用的问题。代码如下:(由于需要更改的代码是在 then 内的,所以其他的这里就不再添加了)

typescript 复制代码
then(onResolve, onReject) {
    // Promise1 是链式后的 Promise
    const Promise1 = new PromiseA((resolve, reject) => {
        if (this.state == 'fulfilled') {
            setTimeout(() => {
                resolvePromise(onResolve(this.value), resolve, reject, Promise1)
            })
        }
        if (this.state == 'rejected') {
            setTimeout(() => {
                resolvePromise(onReject(this.reason), resolve, reject, Promise1)
            })
        }
        if (this.state == 'pending') {
            this.onResolveBack.push(() => {
                setTimeout(() => {
                    resolvePromise(onResolve(this.value), resolve, reject, Promise1)
                })
            })
            this.onRejectBack.push(() => {
                setTimeout(() => {
                    resolvePromise(onReject(this.reason), resolve, reject, Promise1)
                })
            })
        }
    })
    return Promise1
}
function resolvePromise(type, resolve, reject, Promise1) {
    // 处理循环调用问题
    if (type === Promise1) {
        const err = `Uncaught(in promise) YypeError: Chaining cycle deteted for promise`
        console.error(err);
        return reject(new TypeError(err))
    }
    // 判断是否是一个Promise
    if (type instanceof PromiseA) {
        type.then(resolve, reject)
    } else {
        resolve(type)
    }
}

以上代码中,我们把 then 方法中的内容使用 PromiseA 包裹了起来,并 return 出去。其中,我们还封装了一个方法用来判断前一个 then 方法的返回值,如果是普通值,直接调用。如果是 Promise 对象,看是否成功,如果成功 resolve ,如果失败 reject 并将值返回。

到这里,我们手写的简易 Promise 就已经完成了。

四、Promise 的四个静态方法

首先是两个最简单的 resolve、reject 方法,如下:

javascript 复制代码
PromiseA.resolve = value => {
    return new PromiseA((resolve, reject) => {
        resolve(value)
    })
}
PromiseA.reject = value => {
    return new PromiseA((resolve, reject) => {
        reject(value)
    })
}

race 方法:

javascript 复制代码
PromiseA.race = (promises) => {
    return new PromiseA((resolve, reject) => {
        for (let i = 0; i < promises.length; i++) {
            promises[i].then(resolve, reject)
        }
    })
}

all 方法:

scss 复制代码
PromiseA.all = (promises) => {
    const arr = []; //所有数据存储的地方
    var i = 0;  //数据累计的地方
    function proscessDate(index, data, resolve) {
        // 每个数据都能按照位置添加进去
        arr[index] = data;
        // 统计当前加载了几个
        i++;
        // 判断当前是否已经加载完成,加载完成使用resolve 整体将数据返回出去
        if (i == promises.length) {
            resolve(arr);
        }
    }
    return new PromiseA((resolve, reject) => {
        for (let j = 0; j < promises.length; j++) {
            promises[j].then((value) => {
                proscessDate(j, value, resolve)
            }, (err) => {
                reject(err)
            })
        }
    })
}

Promise 的四个静态方法具体的使用,请参考《阮一峰 ECMAScript 6 (ES6) 标准入门教程 第三版》

今天的分享就到这里了,感谢您的观看!

相关推荐
架构师汤师爷2 分钟前
一文彻底搞懂 OpenClaw 的架构设计与运行原理(万字图文)
前端·agent
苑若轻航3 分钟前
防抖和节流:解决高频事件性能
前端
小黑的铁粉5 分钟前
什么是事件循环?调用堆栈和任务队列之间有什么区别?
前端·javascript
小黑的铁粉6 分钟前
常见的内存泄漏有哪些?
前端·javascript
喝水的长颈鹿6 分钟前
JavaScript 基础入门
前端
喝咖啡的女孩8 分钟前
call、apply、bind 原理与实现
前端
雨落Re8 分钟前
从设计到开发,过年我用十天使用AI搭建了一个完整的博客系统
前端·后端
冴羽17 分钟前
100s 带你了解 Bun 为什么这么火
前端·node.js·bun
Sylvia33.21 分钟前
火星数据:解构斯诺克每一杆进攻背后的数字语言
java·前端·python·数据挖掘·数据分析
Wect38 分钟前
LeetCode 530. 二叉搜索树的最小绝对差:两种解法详解(迭代+递归)
前端·算法·typescript