13 # promise 的状态更改问题

简介: 13 # promise 的状态更改问题

下面需要实现两个功能,一个是下面这种 resolve 了一个 promise,还能等待这个 promise 的状态去返回。

另外一个就是如果同时调用了 resolvePromise 和 rejectPromise,或者对同一参数进行了多次调用,则第一次调用优先,任何进一步的调用都将被忽略。

没有实现前,我们目前返回的是一个promise:

下面实现:

// promise 就是一个类
// 1. promise 有三个状态:成功态(resolve) 失败态(reject) 等待态(pending)(又不成功又不失败)
// 2. 用户自己决定失败的原因和成功的原因,成功和失败也是用户定义的
// 3. promise 默认执行器立即执行
// 4. promise 的实例都拥有一个 then 方法,一个参数是成功的回调,另一个是失败的回调
// 5. 如果执行函数时发生了异常也会执行失败的逻辑
// 6. 如果 promise 一旦成功就不能失败,反之亦然,只有等待态的时候才能去更新状态
const RESOLVE = "RESOLVE"; // 成功态
const REJECT = "REJECT"; // 失败态
const PENDING = "PENDING"; // 等待态
// 解析 promise:所有的promise需要兼容,比如 bluebird q es6-promise
const resolvePromise = (promise2, x, resolve, reject) => {
    // 处理一下循环引用(自己等待自己的错误实现),这个时候我们用一个类型错误结束掉 promise。
    if (promise2 === x) {
        reject(new TypeError("Chaining cycle detected for promise #<Promise>"));
    }
    // 用于防止走成功又走失败
    let called;
    // 后续的条件要严格判断 保证代码能和别的库一起使用
    if ((typeof x === "object" && x !== null) || typeof x === "function") {
        // 要继续判断是否是一个promise
        try {
            // x.then 取值时可能会报错,需要捕获异常
            let then = x.then;
            // then 是个函数我们就认为是promise
            if (typeof then === "function") {
                // 下面为什么不写成 x.then 的原因,可能第二次取出报错,直接写成 then.call(x)
                // 根据 promise 的状态决定是成功还是失败
                then.call(
                    x,
                    (y) => {
                        if (called) return;
                        called = true;
                        // 递归解析
                        resolvePromise(promise2, y, resolve, reject);
                    },
                    (e) => {
                        if (called) return;
                        called = true;
                        reject(e);
                    }
                );
            } else {
                resolve(x);
            }
        } catch (e) {
            // 防止失败了再次进入成功
            if (called) return;
            called = true;
            reject(e);
        }
    } else {
        resolve(x);
    }
};
class KaimoPromise {
    constructor(executor) {
        this.status = PENDING;
        // value 是任意合法的 Javascript 值,(包括 undefined,thenable, promise)。
        this.value = undefined;
        // reason 是表示 promise 为什么被 rejected 的值
        this.reason = undefined;
        // 存放成功回调
        this.onResolveCallbacks = [];
        // 存放失败回调
        this.onRejectCallbacks = [];
        let resolve = (value) => {
            if (this.status === PENDING) {
                this.value = value;
                this.status = RESOLVE;
                this.onResolveCallbacks.forEach((fn) => fn());
            }
        };
        let reject = (reason) => {
            if (this.status === PENDING) {
                this.reason = reason;
                this.status = REJECT;
                this.onRejectCallbacks.forEach((fn) => fn());
            }
        };
        try {
            // 立即执行
            executor(resolve, reject);
        } catch (error) {
            // 处理错误异常
            console.log("inner---->", error);
            reject(error);
        }
    }
    // 1. promise 成功和失败的回调的返回值可以传递到外层的下一个 then
    // 2. promise 返回值情况
    //  2.1. 如果返回的是普通的值的话:传递到下一次的成功中(不是错误也不是 promise 就是普通值)
    //  2.2. 如果返回的是错误的情况:一定会走到下一次的失败中
    //  2.3. 如果返回的是 promise 的情况:会采用 promise 的状态,决定下一步是走成功还是失败
    // 3. 错误处理:如果离自己最近的 then 没有错误处理(没有写错误函数)会向下找
    // 4. 每次执行完 `promise.then` 方法后返回的都是一个新的 promise (promise 一旦成功或者失败都不能去修改状态)
    // promise 必须提供 then 方法来存取它当前或最终的值或者原因。
    // then 接收两个参数:onFulfilled 和 onRejected
    then(onFulfilled, onRejected) {
        // 实现链式调用
        let promise2 = new KaimoPromise((resolve, reject) => {
            if (this.status === RESOLVE) {
                // setTimeout 处理 promise2 没有初始化的问题, 需要等 executor 执行完
                setTimeout(() => {
                    // try catch 无法捕获异步代码,所以需要在 promise2 里面也加 try catch
                    try {
                        let x = onFulfilled(this.value);
                        // x 可能是一个 promise
                        resolvePromise(promise2, x, resolve, reject);
                    } catch (e) {
                        reject(e);
                    }
                }, 0);
            }
            if (this.status === REJECT) {
                setTimeout(() => {
                    try {
                        let x = onRejected(this.reason);
                        resolvePromise(promise2, x, resolve, reject);
                    } catch (e) {
                        reject(e);
                    }
                }, 0);
            }
            // 下面是异步的,可以不加setTimeout,统一一下加了一下
            if (this.status === PENDING) {
                this.onResolveCallbacks.push(() => {
                    setTimeout(() => {
                        try {
                            let x = onFulfilled(this.value);
                            resolvePromise(promise2, x, resolve, reject);
                        } catch (e) {
                            reject(e);
                        }
                    }, 0);
                });
                this.onRejectCallbacks.push(() => {
                    setTimeout(() => {
                        try {
                            let x = onRejected(this.reason);
                            resolvePromise(promise2, x, resolve, reject);
                        } catch (e) {
                            reject(e);
                        }
                    }, 0);
                });
            }
        });
        return promise2;
    }
}
module.exports = KaimoPromise;

测试:

const KaimoPromise = require("./13/kaimo-promise.js");
let p1 = new KaimoPromise((resolve, reject) => {
    resolve("kaimo");
});
let promise2 = p1.then((data) => {
    console.log("p1---resolve--->", data);
    return new KaimoPromise((resolve, reject) => {
        setTimeout(() => {
            resolve(
                new KaimoPromise((resolve, reject) => {
                    setTimeout(() => {
                        resolve("kaimo666");
                    }, 1000);
                })
            );
        }, 1000);
    });
});
promise2.then(
    (data) => {
        console.log("p2---resolve--->", data);
    },
    (err) => {
        console.log("p2---reject--->", err);
    }
);

这样我们就实现了:

目录
相关文章
|
JavaScript 前端开发 编译器
看完这篇文章,不再害怕Vue3的源码(一)
看完这篇文章,不再害怕Vue3的源码
|
前端开发 API
vue3+TS实战中Dialog弹窗封装复用的技巧
Dialog弹窗在后台管理系统中是使用频率非常高的组件,添加和修改数据基本都会用到,本文就讲讲在vue3和ts的项目中如何封装Dialog组件,实现代码高复用
3458 0
vue3+TS实战中Dialog弹窗封装复用的技巧
|
应用服务中间件 nginx
nginx优化:URI过长或request header过大导致400或414报错
当出现URI过长或请求头过大导致400或414报错时,可以通过以下方式对Nginx进行优化: 1. 调整client_max_body_size参数:该参数用于限制请求体的大小。默认情况下,Nginx的client_max_body_size参数设置为1M。如果请求体超过这个大小,Nginx会返回400错误。您可以根据实际需求适当增加这个值,例如设置为10M或更大。 ``` http { client_max_body_size 10M; } ``` 2. 调整large_client_header_buffers参数:该参数用于调整请求头缓冲区的大
6998 0
|
Web App开发 前端开发 JavaScript
构建响应式Web界面:现代前端开发的最佳实践
【2月更文挑战第15天】 在多设备浏览时代,响应式Web设计成为前端开发者的必备技能。本文将深入探讨实现响应式界面的核心概念、技术栈以及工具,帮助读者掌握从布局到交互的全方位解决方案。通过灵活运用CSS框架、媒体查询及JavaScript,开发者可以创建出适应不同屏幕尺寸和分辨率的网站。文章不仅涵盖理论分析,还包含实际案例,确保读者能够将知识应用于实际项目中。
|
前端开发 JavaScript
用iframe的方式实现微前端
微前端是最近几年火起来的概念,iframe是早期实现微前端的理想方案,而现在有了其它的方案,比如qianduan框架,single-spa,以及webpack5带来的联邦模块方案。但是每一个方案都有其优缺点,感兴趣的可以去实践一下。
|
缓存 前端开发 JavaScript
终极 Nginx 配置指南(全网最详细)
本文详细介绍了Nginx配置文件`nginx.conf`的基本结构及其优化方法。首先通过删除注释简化了原始配置,使其更易理解。接着,文章将`nginx.conf`分为全局块、events块和http块三部分进行详细解析,帮助读者更好地掌握其功能与配置。此外,还介绍了如何通过简单修改实现网站上线,并提供了Nginx的优化技巧,包括解决前端History模式下的404问题、配置反向代理、开启gzip压缩、设置维护页面、在同一IP上部署多个网站以及实现动静分离等。最后,附上了Nginx的基础命令,如安装、启动、重启和关闭等操作,方便读者实践应用。
5227 85
终极 Nginx 配置指南(全网最详细)
|
11月前
|
API
查手机号归属地免费API接口教程
此API用于查询指定手机号码的归属地信息,包括号段、省份、城市、运营商等。支持POST和GET请求方式,需提供用户ID、KEY及手机号作为参数。返回结果包含状态码、信息提示及详细归属地信息。示例请求地址:https://cn.apihz.cn/api/ip/shouji.php?id=88888888&key=88888888&phone=13219931963。
1620 5
|
12月前
|
应用服务中间件 nginx
ThreeJs导入外部3D模型
这篇文章详细介绍了如何在Three.js中导入并显示外部的3D模型,包括所需的准备工作和具体实现步骤。
941 0
|
JavaScript 前端开发 UED
​基于 vue + element plus + node 实现大文件分片上传,断点续传和秒传的功能!牛哇~
​基于 vue + element plus + node 实现大文件分片上传,断点续传和秒传的功能!牛哇~
|
资源调度 JavaScript Apache
Vue2使用echarts树图(tree)
这篇文章介绍了如何在Vue 3框架中集成echarts库来创建一个树状图(Tree Chart)组件,支持自定义数据和交互事件。
837 0
Vue2使用echarts树图(tree)