用专门的数据类型描述异常,能便于串联可能出错的操作。Option类型就是这张容器,在没有值时也能串联操作。
js中常用Promise来处理异步调用,本文将手动实现Promise的部分功能,来探索这个类型的处理逻辑。
TS表示和处理错误的常用模式有:
1)返回null。
2)抛出异常。
3)返回异常。
4)Option类型。
这些处理机制各有千秋,可根据实际的业务场景来选择使用。
在遇到错误时,直接返回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太繁琐,不利于嵌套和串联操作。
当遇到错误时直接抛出异常。
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中,不会去检查异常。
在遇到错误时,直接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);
}
这种方式比较轻量,又能提供错误信息,能强制使用方处理每一个异常。但是在串联和嵌套操作时会比较繁琐。
Option不是JS内置的数据类型,需要我们自己编写。它表示不返回一个值,而是返回一个容器。该容器可能有一个值,也可能没有。这个容器有一些方法,让没有值时也能串联操作。
class CustomOption {
constructor(private val: T) {
}
then(fun: (val:T) => CustomOption) {
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 {
then(fun: (val:T) => CustomOption): CustomOption;
getValue(val:T):T;
}
class CustomSome implements CustomOption {
constructor(private value: T) {
}
getValue(): T {
return this.value;
}
then(fun: (val:T) => CustomOption): CustomOption {
return fun(this.value);
}
}
class CustomNone implements CustomOption {
getValue(val: T): T {
return val;
}
then(): CustomOption {
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);
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);
})
}
});
在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;
private rejectCallbackList: Array;
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;
private rejectCallbackList: Array;
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);
});