TS Option类型与Promise

用专门的数据类型描述异常,能便于串联可能出错的操作。Option类型就是这张容器,在没有值时也能串联操作。

js中常用Promise来处理异步调用,本文将手动实现Promise的部分功能,来探索这个类型的处理逻辑。

1 处理错误

TS表示和处理错误的常用模式有:

1)返回null。

2)抛出异常。

3)返回异常。

4)Option类型。

这些处理机制各有千秋,可根据实际的业务场景来选择使用。

1.1 处理错误的常规模式

1.1.1 返回null

在遇到错误时,直接返回null,其他情况下返回正常的值。

复制代码
function parseDate() {
    let num = Math.random();
    if (num > 0.5) {  // 模拟遇到错误的场景
        return null;
    }
    return new Date();
}

在调用此类函数时,需要先检查它的返回结果,然后再使用。

复制代码
let result = parseDate();
if (result) {
    console.log(result);
} else {
    console.log("出错啦");
}

这种方式是处理错误最为轻量的方式,但是无法返回出错原因,且返回的null也不利于程序的编写,每次操作都需要检查返回结果是否为null太繁琐,不利于嵌套和串联操作。

1.1.2 抛出异常

当遇到错误时直接抛出异常。

复制代码
function parseDate() {
    let num = Math.random();
    if (num > 0.5) {  // 模拟遇到错误的场景
        throw new RangeError("随机数值小于0.5");
    }
    return new Date();
}

在调用此类函数时,需要使用try/catch来捕获错误。

复制代码
try {
    console.log(parseDate());
} catch (error) {
    console.error(error);
}

这种方式可以指出失败原因,但是需要使用try/catch来捕获。这样在串联或嵌套操作时将会使代码结构变得复杂。而在实际开发中,开发人员可能不会将代码放在try/catch中,不会去检查异常。

1.1.3 返回异常

在遇到错误时,直接return 异常。

复制代码
function parseDate() {
    let num = Math.random();
    if (num > 0.5) {  // 模拟遇到错误的场景
        return  new RangeError("随机数值小于0.5");
    }
    return new Date();
}

在调用此类函数时,需要根据返回值类型做不同的处理。

复制代码
let result = parseDate();
if (result instanceof Error) {
    console.error("出错了:" + result.message);
} else {
    console.log(result);
}

这种方式比较轻量,又能提供错误信息,能强制使用方处理每一个异常。但是在串联和嵌套操作时会比较繁琐。

1.2 Option类型

Option不是JS内置的数据类型,需要我们自己编写。它表示不返回一个值,而是返回一个容器。该容器可能有一个值,也可能没有。这个容器有一些方法,让没有值时也能串联操作。

复制代码
class CustomOption<T> {

    constructor(private val: T) {
    }

    then<U>(fun: (val:T) => CustomOption<U>) {
        return fun(this.val);
    }

    getValue(errorMessage: string) {
        return this.val || errorMessage;
    }
}

function askInput() {
    let num = Math.random();
    if (num > 0.5) {
        return new CustomOption(null)
    }
    return new CustomOption("2023-12-16 12:34:24");
}

function parseDate(str: string | null) {
    if (str) {
        return new CustomOption(new Date(str));
    } else {
        return new CustomOption(null);
    }
}

let result = askInput().then(parseDate).getValue("转化失败");
console.log(result);

上面代码有两个问题:1)实际上当第一个函数报错的时候就意味着整个调用链都出错了,因此在后面的调用上,应该及时知晓上个返回值是个空值,以免执行不必要的操作。2)在调用getValue这个参数时,当前一个函数返回正常值时是不需要往getValue传递参数的。

下面是优化后的代码:

复制代码
interface CustomOption<T> {
    then<U>(fun: (val:T) => CustomOption<U>): CustomOption<U>;
    getValue(val:T):T;
}

class CustomSome<T> implements CustomOption<T> {
    constructor(private value: T) {
    }

    getValue(): T {
        return this.value;
    }

    then<U>(fun: (val:T) => CustomOption<U>): CustomOption<U> {
        return fun(this.value);
    }
}

class CustomNone implements CustomOption<never> {
    getValue<T>(val: T): T {
        return val;
    }

    then<U>(): CustomOption<U> {
        return this;
    }
}

function askInput() {
    let num = Math.random();
    if (num > 0.5) {
        return new CustomNone()
    }
    return new CustomSome("2023-12-16 12:34:24");
}

function parseDate(str: string | null) {
    if (str) {
        return new CustomSome(new Date(str));
    } else {
        return new CustomNone();
    }
}

let result = askInput().then(parseDate).getValue("转化失败");
console.log(result);

2 处理回调

JS异步程序的核心基础是回调。回调其实就是常规函数,只是作为参数传给另一个函数。就像在同步程序一样,另一个函数在操作完成后调用会调函数。

但在串联和嵌套操作中,容易导致"回调金字塔"。

复制代码
function askInput(callback: (val: string) => void) {
    let num = Math.random();
    if (num > 0.5) {
        throw new RangeError("随机数小于0.5");
    } else {
        callback("2023-12-13 12:00");
    }
}

function parseDate(str: string,callback: (date: Date) => void) {
   callback(new Date(str));
}

askInput((val: string) => { // 嵌套第一层
    if (val) {
        parseDate(val,(date:Date) => { // 嵌套第二层
            console.log(date);
        })
    }
});

2.1 Promise

在JS中,常用Promise来解决"回调金字塔"问题。

1)实现Promise的then方法来处理回调。

复制代码
type Executor = (
    resolve: ExecutorResolve,
    reject: ExecutorReject
) => void

type CustomPromiseStatus = "pending" | "finished" | "rejected";
type ExecutorResolve = (val: any)=>void;
type ExecutorReject = (error: any) =>void;

class CustomPromise {

    private status: CustomPromiseStatus;
    private value: any;
    private error: null;
    private resolveCallbackList: Array<ExecutorResolve>;
    private rejectCallbackList: Array<ExecutorReject>;

    constructor(executor: Executor) {
        this.status = "pending";
        this.value = null;
        this.error = null;
        this.resolveCallbackList = [];
        this.rejectCallbackList = [];

        const resolve:ExecutorResolve = (val: any) => {
            this.status = "finished";
            this.value = val;
            try {
                this.resolveCallbackList.forEach(callback => callback(this.value));
            } catch (e) {
                reject(e);
            }
        };

        const reject:ExecutorReject = (error: any) => {
            this.status = "rejected";
            this.error = error;
            this.rejectCallbackList.forEach(callback => callback(error));
        }
        executor(resolve,reject);
    }

    then(resolve:ExecutorResolve,reject: ExecutorReject) {
        switch (this.status) {
            case "finished":
                try {
                    resolve(this.value);
                } catch (e) {
                    reject(e);
                }
                break;
            case "rejected":
                reject(this.error);
                break;
            default:
                this.resolveCallbackList.push(resolve);
                this.rejectCallbackList.push(reject);
        }
    }
}

function askInputPromise() {
    return new CustomPromise((resolve, reject) => {
        let num = Math.random();
        if (num > 0.5) {
            reject(new RangeError("随机数小于0.5"));
        } else {
            resolve("2023-12-16 12:34:24");
        }
    })
}

let promise = askInputPromise();
promise.then((val:string) => {
    console.log(new Date(val));
},(error) => {
    console.log("出错啦:" + error);
})

2)then 方法返回Promise类型,实现可串联使用。

复制代码
type Executor = (
    resolve: ExecutorResolve,
    reject: ExecutorReject
) => void

type CustomPromiseStatus = "pending" | "finished" | "rejected";
type ExecutorResolve = (val: any)=> any;
type ExecutorReject = (error: any) =>void;

let IsPromise = (val: any) : val is CustomPromise => {
    return val instanceof CustomPromise;
};

class CustomPromise {

    private status: CustomPromiseStatus;
    private value: any;
    private error: null;
    private resolveCallbackList: Array<ExecutorResolve>;
    private rejectCallbackList: Array<ExecutorReject>;

    constructor(executor: Executor) {
        this.status = "pending";
        this.value = null;
        this.error = null;
        this.resolveCallbackList = [];
        this.rejectCallbackList = [];

        const resolve:ExecutorResolve = (val: any) => {
            this.status = "finished";
            this.value = val;
            try {
                this.resolveCallbackList.forEach(callback => callback(this.value));
            } catch (e) {
                reject(e);
            }
        };

        const reject:ExecutorReject = (error: any) => {
            this.status = "rejected";
            this.error = error;
            this.rejectCallbackList.forEach(callback => callback(error));
        }
        executor(resolve,reject);
    }

    then(resolve:ExecutorResolve,reject?: ExecutorReject) {

        return new CustomPromise((resolveNew, rejectNew) => {

            const resolveFun:ExecutorResolve = (val: any) => {
                try {
                    let res = resolve(this.value);
                    if (IsPromise(res)) {
                        res.then(resolveNew);
                    } else {
                        resolveNew(res);
                    }
                } catch (e) {
                    rejectNew(e);
                }
            };

            const rejectFun: ExecutorReject = (error: any) => {
                if (reject) {
                    reject(error);
                }
                if (rejectNew) {
                    rejectNew(error);
                }
            }

            switch (this.status) {
                case "finished":
                    try {
                        resolveFun(this.value);
                    } catch (e) {
                        rejectFun(e);
                    }
                    break;
                case "rejected":
                    rejectFun(this.error);
                    break;
                default:
                    this.resolveCallbackList.push(resolveFun);
                    this.rejectCallbackList.push(rejectFun);
            }
        })
    }
}

function askInputPromise() {
    return new CustomPromise((resolve, reject) => {
        let num = Math.random();
        if (num > 0.5) {
            reject(new RangeError("随机数小于0.5"));
        } else {
            resolve("2023-12-16 12:34:24");
        }
    })
}

askInputPromise().then((val:string) => {
    return new CustomPromise((resolve, reject) => {
        console.log("输入参数:" + val);
        resolve(new Date(val));
    })
}).then((val:any) => {
    console.log("最终结果:",val)
},(error: any) => {
    console.log("在这里就出错了",error);
});
相关推荐
QQ1__8115175157 小时前
Spring boot名城小区物业管理系统信息管理系统源码-SpringBoot后端+Vue前端+MySQL【可直接运行】
前端·vue.js·spring boot
钛态7 小时前
前端微前端架构:大项目的救命稻草还是自找麻烦?
前端·vue·react·web
一粒黑子7 小时前
【实战解析】阿里开源 PageAgent:纯前端 GUI Agent,一行JS让网页支持自然语言操控
前端·javascript·开源
独角鲸网络安全实验室7 小时前
2026微信小程序抓包全解析:从实操落地到合规风控,解锁前端调试新范式
前端·微信小程序·小程序·抓包·系统代理绕过·https证书严格校验·进程隔离
紫微AI7 小时前
前端文本测量成了卡死一切创新的最后瓶颈,pretext实现突破了
前端·人工智能·typescript
GISer_Jing7 小时前
AI前端(From豆包)
前端·aigc·ai编程
IT枫斗者7 小时前
前端部署后如何判断“页面是不是最新”?一套可落地的版本检测方案(适配 Vite/Vue/React/任意 SPA)
前端·javascript·vue.js·react.js·架构·bug
测试修炼手册7 小时前
[测试技术] 深入理解 JSON Web Token (JWT)
前端·json
AI老李7 小时前
2026 年 Web 前端开发的 8 个趋势!
前端
里欧跑得慢7 小时前
15. Web可访问性最佳实践:让每个用户都能平等访问
前端·css·flutter·web