侯体宗的博客
  • 首页
  • Hyperf版
  • beego仿版
  • 人生(杂谈)
  • 技术
  • 关于我
  • 更多分类
    • 文件下载
    • 文字修仙
    • 中国象棋ai
    • 群聊
    • 九宫格抽奖
    • 拼图
    • 消消乐
    • 相册

手写JS实现Promise

前端  /  管理员 发布于 7年前   369

Promise概览

Promise是一个管理异步编程的方案,它是一个构造函数,每次使用可用new创建实例;它有三种状态:pending、fulfilled和rejected,这三种状态不会受外界影响,状态只能由pending变为fullfilled(成功),pending变为rejected(失败),且一旦改变就不会再改变,在状态改变后,它会返回成功的结果或者失败的原因,它对外抛出了resolve、reject、catch、finally、then、all、race、done,在最新的提案中,添加了allSettled方法,它不管成功、失败都会返回,接下来,我们自己实现整个Promise

executor函数

我们知道,在创建一个Promise实例时,都会立即执行executor函数,executor函数传递两个参数,resolve和reject,如果executor函数执行错误,Promise实例状态会变为rejected

class MyPromise{    constructor(executor) {        this.status = "pending";     // 初始化状态为pending        this.value = undefined;      // 初始化返回的成功的结果或者失败的原因    // 这里是resolve方法,成功后执行,将状态改变为resolved,并且将结果返回        let resolve = result => {if(this.status !== "pending") return;  // 状态一旦改变,就不会再变this.status = "resolved";this.value = result;        }    // 这里是reject方法,异常时执行,状态改为rejected,并且将失败的原因返回        let reject = reason => {if(this.status !== "pending") return;this.status = "rejected";this.value = reason;        }        // try、catch捕获异常,如果错误,执行reject方法        try {executor(resolve, reject)        } catch(err) {reject(err)        }    }}

我们来验证一下,现在的Promise是什么样的

let p1 = new MyPromise((resolve, reject) => {    resolve(1);})let p2 = new MyPromise((resolve, reject) => {    reject(2);})console.log(p1);console.log(p2);

可以看到,状态已经改变了,里面的值也是成功的结果和失败的原因。then方法有两个参数,第一个参数是成功时执行的,第二个参数为失败后执行的,then的链式调用和数组等是一样的,每次执行后会返回一个Promise实例。如果成功后,第一个then中成功的函数为null,它会继续向下查找,直至不为null的函数执行,上一个then中返回的结果会直接影响下一个then中执行成功或者失败的哪个函数,了解了这些之后,我们尝试实现一下~

then方法

then(resolveFn, rejectFn) {    // 如果传入的两个参数不是函数,则直接执行返回结果    let resolveArr = [];    let rejectArr = [];        if(typeof resolveFn !== "function") {        resolveFn = result => {return result;        }    }        if(typeof rejectFn !== "function") {        rejectFn = reason => {return MyPromise.reject(reason);        }    }        return new Mypromise((resolve, reject) => {        resolveArr.push(result => {try {    let x = resolveFn(result);        if(x instanceof MyPromise) {        x.then(resolve, reject)        return;    }        resolve(x);} catch(err) {    reject(err)}        })    rejectArr.push(reason => {try {    let x = rejectFn(reason);        if(x instanceof MyPromise) {        x.then(resolve, reject)        return;    }        resolve(x);} catch(err) {    reject(err)}        })    })}

我们来整理一下上面的代码

class MyPromise{    constructor(executor) {        this.status = "pending";     // 初始化状态为pending        this.value = undefined;      // 初始化返回的成功的结果或者失败的原因        this.resolveArr = [];        // 初始化then中成功的方法        this.rejectArr = [];         // 初始化then中失败的方法// 定义change方法,因为我们发现好像resolve和reject方法共同的地方还挺多        let change = (status, value) => {if(this.status !== "pending") return;  // 状态一旦改变,就不会再变this.status = status;this.value = value;// 根据状态判断要执行成功的方法或失败的方法let fnArr = status === "resolved" ? this.resolveArr : this.rejectArr;// fnArr中的方法依次执行fnArr.forEach(item => {    if(typeof item !== "function") return;    item(this. value);})        }        // 这里是resolve方法,成功后执行,将状态改变为resolved,并且将结果返回        let resolve = result => {change("resolved", result)        }    // 这里是reject方法,异常时执行,状态改为rejected,并且将失败的原因返回        let reject = reason => {change("rejected", reason);        }    // try、catch捕获异常,如果错误,执行reject方法        try {executor(resolve, reject)        } catch(err) {reject(err)        }    }        then(resolveFn, rejectFn) {    // 如果传入的两个参数不是函数,则直接执行返回结果if(typeof resolveFn !== "function") {resolveFn = result => {    return result;}        }    if(typeof rejectFn !== "function") {rejectFn = reason => {    return MyPromise.reject(reason);}        }    return new MyPromise((resolve, reject) => {this.resolveArr.push(result => {    try {        let x = resolveFn(result);  // 获取执行成功方法返回的结果    // 如果x是一个promise实例,则继续调用then方法 ==> then链的实现        if(x instanceof MyPromise) {x.then(resolve, reject)return;        }    // 不是promise实例,直接执行成功的方法        resolve(x);    } catch(err) {        reject(err)    }})this.rejectArr.push(reason => {    try {        let x = rejectFn(reason);    if(x instanceof MyPromise) {x.then(resolve, reject)return;        }    resolve(x);    } catch(err) {        reject(err)    }})        })    }}

我们来看一下效果

new MyPromise((resolve, reject) => {    resolve(1);}).then(res => {    console.log(res, 'success');}, err => {    console.log(err, 'error');})

这时候,问题出现了,我们发现好像什么也没有输出,如果我们对上面的测试例子做一下小小的改动呢?

new MyPromise((resolve, reject) => {    setTimeout(_ => {        resolve(1);    }, 0)}).then(res => {    console.log(res, 'success');    // 1 "success"}, err => {    console.log(err, 'error');})

这是因为创建了Promise实例就立即执行了executor函数,还没有执行then方法,那么不管成功还是失败的数组中,都是空的。那可能小伙伴们又有疑问了,为什么加了setTimeout就好使了呢?这是因为在事件队列机制中,setTimeout会放入事件队列中,等主线程执行完成后再执行,此时then方法会存储成功或者失败的函数,所以不管是成功的数组还是失败的数组中都已经有值了,这个时候再去执行就完全了~

但是我们不能在使用的时候写setTimeout当做解决方案呀,既然我们在封装,就要在封装的函数内解决问题,按照这样的思路,我们也同样可以在resolve和reject方法执行的时候,判断数组中是否有值,如果没有,我们可以利用setTimeout让它延后执行,代码如下~

// 这里是resolve方法,成功后执行,将状态改变为resolved,并且将结果返回let resolve = result => {       // 如果数组中有值,则立即改变状态    if(this.resolveArr.length > 0) {        change("resolved", result)    }    // 如果没值,则延后改变状态    let timer = setTimeout(_ => {        change("resolved", result)        clearTimeout(timer);    }, 0)}// 这里是reject方法,异常时执行,状态改为rejected,并且将失败的原因返回let reject = reason => {// 如果数组中有值,则立即改变状态    if(this.rejectArr.length > 0) {        change("rejected", reason);    }    // 如果没值,则延后改变状态    let timer = setTimeout(_ => {        change("rejected", reason);        clearTimeout(timer);    }, 0)}

现在我们再试一下

// 1、已经成功了new MyPromise((resolve, reject) => {    resolve('我成功啦,吼吼吼~~~~');    reject('我都已经成功了,你别想让我失败,哼~~');}).then(res => {    console.log(res, 'success');         // 我成功啦,吼吼吼~~~~ success}, err => {    console.log(err, 'error');})// 2、先失败了new MyPromise((resolve, reject) => {    reject('失败了,我好委屈,呜呜呜~~');    resolve('已经失败了~~~');}).then(res => {    console.log(res, 'success');         }, err => {    console.log(err, 'error');          // 失败了,我好委屈,呜呜呜~~ error})// 3、链式调用new MyPromise((resolve, reject) => {    reject('失败了,我好委屈,呜呜呜~~');    resolve('已经失败了~~~');}).then(res => {    console.log(res);}, err => {    console.log(err, 'error');          // 失败了,我好委屈,呜呜呜~~ error    return '我要发奋图强,不会被困难所击倒,我要成功!!!'}).then(res1 => {    console.log(res1, '经过不懈努力,我终于在第二次成功了~');  // 我要发奋图强,不会被困难所击倒,我要成功!!!  经过不懈努力,我终于在第二次成功了~}, err1 => {    console.log(err1, '第二次失败');})

这就完美解决了第一次调用,不会执行then方法的问题。同时,实现了链式的调用。对于链式的调用,我多啰嗦两句,其实不管是数组的链式调用,都是因为上一次返回的还是此实例。

catch方法

catch方法是捕获异常,它和then方法的第二个回调函数是一样的

catch(rejectFn) {    return this.then(null, rejectFn)}

resolve方法

我们知道,Promsie也可以这样用

let p1 = MyPromise.resolve(1);console.log(p1);

我们期望有这样一种写法,但是现在肯定会抛出错误:MyPromise.resolve不是一个方法

现在需要我们封装一下resolve方法,我们需要明确的是,resolve之后,Promise是支持再继续链式调用then的,所以,我们需要执行resolve方法,返回一个Promise实例

static resolve(result) {    // 返回新的promise实例,执行promise实例中resolve方法    return new MyPromise(resolve => {        resolve(result)    })}

reject方法

像resolve方法一样,只不过它接收的是失败的函数

static reject(reason) {    // 返回新的promise实例,执行promise实例中reject方法    return new MyPromise((_, reject) => {        reject(reason);    })}

done方法

ES6标准入门一书中,对done方法的解释是这样的:无论Promise对象的回调链以then方法还是catch方法结尾,只要最后一个方法抛出错误,都有可能无法捕获到。为此,Promise提供了一个done方法,它总是处于回掉链的尾端,保证抛出任何可能出现的错误。好了,我们知道了这个方法是干啥的,现在就开始写吧~

done(resolveFn, rejectFn) {    this.then(resolveFn, rejectFn)        .catch(reason => {setTimeout(() => {    throw reason;}, 0)        })}

它可以接收fulfilled、rejected状态的回调函数,也可以不提供任何参数。但是无论怎样,done方法都会捕捉到任何可能出现的错误,并向全局抛出

finally方法

finally方法是无论成功还是失败都会执行的方法,像这样的方法还有小程序中的complete方法等等,我们来尝试实现一下~

finally(finallyFn) {    let P = this.constructor;    return this.then(        value => P.resolve(finallyFn()).then(() => value),        reason => P.reject(finallyFn()).then(() => reason)    )}

我们来验证一下

new MyPromise((resolve, reject) => {    reject('失败了,我好委屈,呜呜呜~~');    resolve('已经失败了~~~');}).then(res => {    console.log(res);}, err => {    console.log(err, 'error');          // 失败了,我好委屈,呜呜呜~~ error    return '我要发奋图强,不会被困难所击倒,我要成功!!!'}).finally(() => {    console.log('执行了吗');// 这里会输出"执行了吗"})

all方法

all方法接收一个数组,当数组中每个实例都成功时才会返回,返回的也是一个数组,每个参数为对应的promise返回的结果,如果有一项失败了,all方法都会返回失败

// 接收数组参数static all(promiseList) {    // 返回新实例,调用后还可使用then、catch等方法    return new MyPromise((resolve, reject) => {        let index = 0,      // 成功次数计数results = [];   // 返回的结果    for(let i = 0; i < promiseList.length; i++) {let item = promiseList[i];// 如果item不是promise实例if(!(item instanceof MyPromise)) return;item.then(result => {    index++;    results[i] = result;    if(index === promiseList.length) {        resolve(results);    }}).catch(reason => {    reject(reason);})        }    })}

来验证一下

// 1.有失败的情况let p1 = MyPromise.resolve(1);let p2 = MyPromise.reject(2);let p3 = MyPromise.resolve(3);MyPromise.all([p1, p2, p3])    .then(res => {        console.log(res);    }).catch(err => {        console.log(err, 'err');     // 2 "err"    })// 2.无失败的情况let p1 = MyPromise.resolve(1);let p2 = MyPromise.resolve(2);let p3 = MyPromise.resolve(3);MyPromise.all([p1, p2, p3])    .then(res => {        console.log(res, 'success');   // [1, 2, 3] "success"    }).catch(err => {        console.log(err, 'err');    })

race方法

race方法同样接收一个数组参数,里面每一项是Promise实例,它返回最快改变状态的Promise实例方法的结果

static race(promiseList) {    return new MyPromise((resolve, reject) => {        promiseList.forEach(item => {if(!(item instanceof MyPromise)) return;item.then(result => {    resolve(result);}).catch(err => {    reject(err)})        })    })}复制代码验证// 1.let p1 = MyPromise.resolve(1);let p2 = MyPromise.reject(2);let p3 = MyPromise.resolve(3);MyPromise.race([p1, p2, p3])    .then(res => {        console.log(res);// 1 'success'    }).catch(err => {        console.log(err, 'err');        })// 2.let p1 = MyPromise.reject(1);let p2 = MyPromise.resolve(2);let p3 = MyPromise.resolve(3);MyPromise.race([p1, p2, p3])    .then(res => {        console.log(res, 'success');       }).catch(err => {        console.log(err, 'err');       // 1 'err'    })    // 3.let p1 = MyPromise.reject(1);let p2 = MyPromise.reject(2);let p3 = MyPromise.reject(3);MyPromise.race([p1, p2, p3])    .then(res => {        console.log(res, 'success');       }).catch(err => {        console.log(err, 'err');       // 1 'err'    })

尝试实现allSettled方法

allSettled方法也是接收数组参数,但是它无论成功或者失败,都会返回

static allSettled(promiseList) {    return new MyPromise((resolve, reject) => {        let results = [];    for(let i = 0; i < promiseList.length; i++) {let item = promiseList[i];if(!(item instanceof MyPromise)) return;item.then(result => {    results[i] = result;}, reason => {    results[i] = reason;})resolve(results);        }    })}复制代码验证// 1.let p1 = MyPromise.resolve(1);let p2 = MyPromise.resolve(2);let p3 = MyPromise.resolve(3);MyPromise.race([p1, p2, p3])    .then(res => {        console.log(res);// [1, 2, 3] 'success'    }).catch(err => {        console.log(err, 'err');        })// 2.let p1 = MyPromise.reject(1);let p2 = MyPromise.reject(2);let p3 = MyPromise.reject(3);MyPromise.race([p1, p2, p3])    .then(res => {        console.log(res, 'success');   // [1, 2, 3] 'success'    }).catch(err => {        console.log(err, 'err');           })    // 3.let p1 = MyPromise.resolve(1);let p2 = MyPromise.reject(2);let p3 = MyPromise.resolve(3);MyPromise.race([p1, p2, p3])    .then(res => {        console.log(res, 'success');   // [1, 2, 3] 'success'    }).catch(err => {        console.log(err, 'err');           })

推荐教程:《JS教程》

以上就是手写JS实现Promise的详细内容,更多请关注其它相关文章!


  • 上一条:
    js不让电脑端浏览器缩放网页
    下一条:
    关于CSS和JS合并的WordPress插件
  • 昵称:

    邮箱:

    0条评论 (评论内容有缓存机制,请悉知!)
    最新最热
    • 分类目录
    • 人生(杂谈)
    • 技术
    • linux
    • Java
    • php
    • 框架(架构)
    • 前端
    • ThinkPHP
    • 数据库
    • 微信(小程序)
    • Laravel
    • Redis
    • Docker
    • Go
    • swoole
    • Windows
    • Python
    • 苹果(mac/ios)
    • 相关文章
    • 使用 Alpine.js 排序插件对元素进行排序(0个评论)
    • 在js中使用jszip + file-saver实现批量下载OSS文件功能示例(0个评论)
    • 在vue中实现父页面按钮显示子组件中的el-dialog效果(0个评论)
    • 使用mock-server实现模拟接口对接流程步骤(0个评论)
    • vue项目打包程序实现把项目打包成一个exe可执行程序(0个评论)
    • 近期文章
    • 在go语言中使用api.geonames.org接口实现根据国际邮政编码获取地址信息功能(1个评论)
    • 在go语言中使用github.com/signintech/gopdf实现生成pdf分页文件功能(0个评论)
    • gmail发邮件报错:534 5.7.9 Application-specific password required...解决方案(0个评论)
    • 欧盟关于强迫劳动的规定的官方举报渠道及官方举报网站(0个评论)
    • 在go语言中使用github.com/signintech/gopdf实现生成pdf文件功能(0个评论)
    • Laravel从Accel获得5700万美元A轮融资(0个评论)
    • 在go + gin中gorm实现指定搜索/区间搜索分页列表功能接口实例(0个评论)
    • 在go语言中实现IP/CIDR的ip和netmask互转及IP段形式互转及ip是否存在IP/CIDR(0个评论)
    • PHP 8.4 Alpha 1现已发布!(0个评论)
    • Laravel 11.15版本发布 - Eloquent Builder中添加的泛型(0个评论)
    • 近期评论
    • 122 在

      学历:一种延缓就业设计,生活需求下的权衡之选中评论 工作几年后,报名考研了,到现在还没认真学习备考,迷茫中。作为一名北漂互联网打工人..
    • 123 在

      Clash for Windows作者删库跑路了,github已404中评论 按理说只要你在国内,所有的流量进出都在监控范围内,不管你怎么隐藏也没用,想搞你分..
    • 原梓番博客 在

      在Laravel框架中使用模型Model分表最简单的方法中评论 好久好久都没看友情链接申请了,今天刚看,已经添加。..
    • 博主 在

      佛跳墙vpn软件不会用?上不了网?佛跳墙vpn常见问题以及解决办法中评论 @1111老铁这个不行了,可以看看近期评论的其他文章..
    • 1111 在

      佛跳墙vpn软件不会用?上不了网?佛跳墙vpn常见问题以及解决办法中评论 网站不能打开,博主百忙中能否发个APP下载链接,佛跳墙或极光..
    • 2016-10
    • 2016-11
    • 2017-06
    • 2017-07
    • 2017-08
    • 2017-09
    • 2017-10
    • 2017-11
    • 2018-03
    • 2018-04
    • 2018-05
    • 2018-06
    • 2018-09
    • 2018-11
    • 2018-12
    • 2019-02
    • 2020-03
    • 2020-04
    • 2020-05
    • 2020-06
    • 2021-04
    • 2021-05
    • 2021-07
    • 2021-08
    • 2021-09
    • 2021-10
    • 2021-11
    • 2022-08
    • 2022-09
    • 2022-10
    • 2022-11
    • 2022-12
    • 2023-01
    • 2023-02
    • 2023-03
    • 2023-04
    • 2023-05
    • 2023-06
    • 2023-07
    • 2023-09
    • 2023-10
    • 2023-11
    • 2023-12
    • 2024-01
    • 2024-02
    • 2024-03
    • 2024-04
    Top

    Copyright·© 2019 侯体宗版权所有· 粤ICP备20027696号 PHP交流群

    侯体宗的博客