用专门的数据类型描述异常,能便于串联可能出错的操作。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
) => voidtype 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
) => voidtype 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);
});