函数式 try-catch 如何转变 JavaScript 代码

开发 前端
我们并没有丢弃它,而是将其转化为更易维护和可预测的工具。 tryCatch()​ 甚至只是许多使用像 try-catch 这样的命令式构造的声明式友好函数之一

这种情况有多常见?

function writeTransactionsToFile(transactions) {
  let writeStatus;
  try {
    fs.writeFileSync('transactions.txt', transactions);
    writeStatus = 'success';
  } catch (error) {
    writeStatus = 'error';
  }
  
  // do something with writeStatus...
}

这是另一个我们想要一个取决于是否存在异常的值的实例。

通常, 我们可能会在 try-catch 的范围之外创建一个可变变量,以便在其中和之后无错误地访问。

但情况并非总是这样。只要有一个函数式的 try-catch 就不会这样。

一个纯粹的 tryCatch() 函数避免了可变变量,并在我们的代码库中鼓励可维护性和可预测性。

没有修改外部状态 - tryCatch() 封装了整个错误处理逻辑并产生单一输出。

我们的 catch 变成了一个不需要大括号的单行代码:

function writeTransactionsToFile(transactions) {
  // 我们现在可以使用 const 了
  const writeStatus = tryCatch({
    tryFn: () => {
      fs.writeFileSync('transactions.txt', transactions);
      return 'success';
    },
    catchFn: (error) => 'error'
  });

  // do something with writeStatus...
}

tryCatch() 函数

那么,这个 tryCatch() 函数究竟是什么样子的呢?

从我们以上的使用方式,你已经可以猜到定义了:

function tryCatch({ tryFn, catchFn }) {
  try {
    return tryFn();
  } catch (error) {
    return catchFn(error);
  }
}

为了正确地讲述函数的作用,我们确保使用对象参数来明确参数名称——即使只有两个属性。

因为编程不仅仅是达到目的的手段 - 我们还在讲述从开始到结束的代码库中的对象和数据的故事。

TypeScript 在这样的情况下非常好用;我们看看一个泛型类型的 tryCatch() 可能是什么样子:

type TryCatchProps<T> = {
  tryFn: () => T;
  catchFn: (error: any) => T;
};

function tryCatch<T>({ tryFn, catchFn }: TryCatchProps<T>): T {
  try {
    return tryFn();
  } catch (error) {
    return catchFn(error);
  }
}

我们用 TypeScript 重写功能性 writeTransactionsToFile() :

function writeTransactionsToFile(transactions: string) {
  // 返回 'success' 或 'error'
  const writeStatus = tryCatch<'success' | 'error'>({
    tryFn: () => {
      fs.writeFileSync('transaction.txt', transactions);
      return 'success';
    },
    catchFn: (error) => return 'error';
  });

  // do something with writeStatus...
}

我们使用 'success' | 'error' 联合类型来限制我们可以从 try 和 catch 回调中返回的字符串。

异步处理

不,我们完全不需要担心这个问题 - 如果 tryFn 或 catchFn 是 async ,那么 writeTransactionToFile() 会自动返回一个 Promise 。

这是我们大多数人应该熟悉的另一个 try-catch 情况:发出网络请求并处理错误。

在这里,我们根据请求是否成功来设置一个外部变量(在try-catch 之外)——在 React 应用中,我们可以轻松地用它设置状态。

显然,在真实世界的应用程序中,请求将会是异步的,以避免阻塞用户界面线程:

async function comment(comment: string) {
  type Status = 'error' | 'success';
  let commentStatus: Status;
  try {
    const response = await fetch('https://api.mywebsite.com/comments', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({ comment }),
    });

    if (!response.ok) {
      commentStatus = 'error';
    } else {
      commentStatus = 'success';
    }
  } catch (error) {
    commentStatus = 'error';
  }

  // do something with commentStatus...
}

我们再次需要在这里创建一个可变变量,以便它可以进入 try-catch 并且没有作用域错误地成功出来。

我们像以前一样进行重构,这次,我们 async 了 try 和 catch 函数,从而 await 了 tryCatch() :

async function comment(comment: string) {
  type Status = 'error' | 'success';
  // ⚠️ await because this returns Promise<Status>
  const commentStatus = await tryCatch<Status>({
    tryFn: async () => {
      const response = await fetch('https://api.mywebsite.com/comments', {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
        },
        body: JSON.stringify({ comment }),
      });
      // ⚠️ functional conditional
      return response.ok ? 'success' : 'error';
    },
    catchFn: async (error) => 'error',
  });

  // do something with commentStatus...
}

可读性,模块化,和单一职责

处理异常时遵循的两个 try-catch 经验法则:

  • try-catch 应尽可能靠近错误的源头
  • 每个函数只使用一个 try-catch

他们将使你的代码在短期和长期内更易于阅读和维护。看看这里的 processJSONFile() 👇,它遵守了规则 1。

第一个 try-catch 仅负责处理文件读取错误,没有其他功能。不会再向 try 添加任何逻辑,所以 catch 也永远不会改变。

接下来的 try-catch 就在这里处理 JSON 解析。

function processJSONFile(filePath) {
  let contents;
  let jsonContents;

  // ✅ 第一个 try-catch 块,用于处理文件读取错误
  try {
    contents = fs.readFileSync(filePath, 'utf8');
  } catch (error) {
    // 在这里记录错误
    contents = null;
  }

  // ✅ 第二个 try-catch 块,用于处理 JSON 解析错误
  try {
    jsonContents = JSON.parse(contents);
  } catch (error) {
    // 在这里记录错误
    jsonContents = null;
  }

  return jsonContents;
}

但是 processJsonFile() 完全无视规则 2,同一个函数中的 try-catch 块都在。

那么,我们通过将它们重构为各自的函数来解决这个问题:

function processJSONFile(filePath) {
  const contents = getFileContents(filePath);
  const jsonContents = parseJSON(contents);

  return jsonContents;
}

function getFileContents(filePath) {
  let contents;
  try {
    contents = fs.readFileSync(filePath, 'utf8');
  } catch (error) {
    contents = null;
  }
  return contents;
}

function parseJSON(content) {
  let json;
  try {
    json = JSON.parse(content);
  } catch (error) {
    json = null;
  }
  return json;
}

但是我们现在有 tryCatch() - 我们可以做得更好:

function processJSONFile(filePath) {
  return parseJSON(getFileContents(filePath));
}

const getFileContents = (filePath) =>
  tryCatch({
    tryFn: () => fs.readFileSync(filePath, 'utf8'),
    catchFn: () => null,
  });

const parseJSON = (content) =>
  tryCatch({
    tryFn: () => JSON.parse(content),
    catchFn: () => null,
  });

我们正在做的只不过是消除异常——这就是这些新功能的主要工作。

如果这种情况经常发生,为什么不创建一个“静音器”版本,在成功时返回 try 函数的结果,或者在错误时什么也不返回?

function tryCatch<T>(fn: () => T) {
  try {
    return fn();
  } catch (error) {
    return null;
  }
}

将我们的代码进一步缩短为这样:

function processJSONFile(filePath) {
  return parseJSON(getFileContents(filePath));
}

const getFileContents = (filePath) => 
  tryCatch(() => fs.readFileSync(filePath, 'utf8'));

const parseJSON = (content) => 
  tryCatch(() => JSON.parse(content));

附注:在命名标识符时,我建议我们尽可能地使用名词来表示变量,形容词来表示函数,而对于高阶函数……我们可以使用副词!就像一个故事,代码将更自然地阅读,并可能更好地理解。

所以,我们可以使用 silently ,而不是 tryCatch :

const getFileContents = (filePath) => 
  silently(() => fs.readFileSync(filePath, 'utf8'));

const parseJSON = (content) => 
  silently(() => JSON.parse(content));

总结

当然, try-catch 本身就能完美运行。

我们并没有丢弃它,而是将其转化为更易维护和可预测的工具。 tryCatch() 甚至只是许多使用像 try-catch 这样的命令式构造的声明式友好函数之一

如果更喜欢直接使用 try-catch ,请记住使用 2 个 try-catch 的经验法则,以提高您的代码的模块化和可读性。

责任编辑:武晓燕 来源: 大迁世界
相关推荐

2024-05-24 08:59:15

2024-11-04 08:20:00

try-catch编程

2009-07-21 14:30:38

Scalatry-catch

2020-05-29 08:14:49

代码Try-Catch程序员

2020-10-14 12:10:22

Javatry-catch代码

2024-05-07 07:58:47

C#程序类型

2017-11-02 15:26:10

JavaScriptasync错误

2022-01-25 12:14:39

面试try-catch代码

2020-08-24 13:35:59

trycatchJava

2020-09-27 07:48:40

不用try catch

2024-11-13 01:00:18

asyncawait​编程

2023-05-16 15:32:45

JavaScriptWeb前端工程师

2023-11-13 17:01:26

C++编程

2009-12-02 19:56:33

PHP中try{}ca

2009-04-10 13:48:17

JavaScripteval全局代码

2023-09-07 07:53:21

JavaScriptGoRust

2021-03-31 11:52:24

try-catch-fJava代码

2024-05-10 11:43:23

C#编程

2024-10-09 08:48:52

2016-10-19 14:35:20

JavaScript函数式编程
点赞
收藏

51CTO技术栈公众号