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) 标准入门教程 第三版》

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

相关推荐
Easonmax9 分钟前
【CSS3】css开篇基础(1)
前端·css
大鱼前端28 分钟前
未来前端发展方向:深度探索与技术前瞻
前端
昨天;明天。今天。33 分钟前
案例-博客页面简单实现
前端·javascript·css
天上掉下来个程小白34 分钟前
请求响应-08.响应-案例
java·服务器·前端·springboot
萧鼎38 分钟前
JavaScript可视化
javascript
周太密1 小时前
使用 Vue 3 和 Element Plus 构建动态酒店日历组件
前端
安冬的码畜日常1 小时前
【玩转 JS 函数式编程_008】3.1.2 JavaScript 函数式编程筑基之:箭头函数——一种更流行的写法
开发语言·javascript·ecmascript·es6·this·箭头函数
时清云1 小时前
【算法】合并两个有序链表
前端·算法·面试
小爱丨同学2 小时前
宏队列和微队列
前端·javascript
沉登c2 小时前
Javascript客户端时间与服务器时间
服务器·javascript