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

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

相关推荐
Angel_girl3198 分钟前
vue项目使用svg图标
前端·vue.js
難釋懷12 分钟前
vue 项目中常用的 2 个 Ajax 库
前端·vue.js·ajax
Qian Xiaoo14 分钟前
Ajax入门
前端·ajax·okhttp
爱生活的苏苏38 分钟前
vue生成二维码图片+文字说明
前端·vue.js
拉不动的猪40 分钟前
安卓和ios小程序开发中的兼容性问题举例
前端·javascript·面试
炫彩@之星1 小时前
Chrome书签的导出与导入:步骤图
前端·chrome
贩卖纯净水.1 小时前
浏览器兼容-polyfill-本地服务-优化
开发语言·前端·javascript
前端百草阁1 小时前
从npm库 Vue 组件到独立SDK:打包与 CDN 引入的最佳实践
前端·vue.js·npm
夏日米米茶1 小时前
Windows系统下npm报错node-gyp configure got “gyp ERR“解决方法
前端·windows·npm
且白2 小时前
vsCode使用本地低版本node启动配置文件
前端·vue.js·vscode·编辑器