在 JavaScript 中编写 Go 式错误处理的 async/await

简介: 笔记

image.png

ES7 引入 async/await 允许开发人员编写看起来像同步的异步 JavaScript 代码。在当前的 JavaScript 版本中,还可以使用 Promises,这些功能都是为了简化异步流程并避免回调地狱。

回调地狱是一个术语,用于描述 JavaScript 中的以下情况:

function asyncTasks() {
    asyncFuncA(function (err, resultA) {
        if (err) return cb(err);
        asyncFuncB(function (err, resultB) {
            if (err) return cb(err);
            asyncFuncC(function (err, resultC) {
                if (err) return cb(err);
                // 更多...
            });
        });
    });
}

上述代码使得维护代码和管理控制流变得非常困难。只需考虑一个 if 语句,如果 callbackA 的某些结果等于 foo,则需要执行其他 async 方法。


拯救 Promises


借助 promisesES6,可以将之前代码的回调噩梦简化为如下内容:

function asyncTasks(cb) {
    asyncFuncA
        .then(AsyncFuncB)
        .then(AsyncFuncC)
        .then(AsyncFuncD)
        .then((data) => cb(null, data))
        .catch((err) => cb(err));
}

虽然从代码阅读来看好很多。但在现实世界的场景中,异步流程可能会变得稍微复杂一些,例如在服务器模型 (nodejs 编程)中,可能想要将一个实体数据保存到数据库中,然后根据保存的值查询其他一些实体,如果该值存在,执行其他一些异步任务,在所有任务完成后,可能想要用步骤 1 中创建的对象响应用户。如果在某个步骤中发生了错误,希望通知用户确切的错误。

当然,使用 promises 看起来会比使用普通回调更干净,但它仍然会变得有点混乱。

关于 Promise 需要的了解的:


async/await


在 ECMAScript 2017 中添加了 async 函数和 await 关键字,并在主流脚本库和其他 JavaScript 编程中得到广泛的应用。


这就是 async/await 真正有用的地方,通过它可以编写下面的代码:

async function asyncTasks(cb) {
    const user = await UserModel.findById(1);
    if (!user) return cb("用户未找到");
    const savedTask = await TaskModel({ userId: user.id, name: "DevPoint" });
    if (user.notificationsEnabled) {
        await NotificationService.sendNotification(user.id, "任务已创建");
    }
    if (savedTask.assignedUser.id !== user.id) {
        await NotificationService.sendNotification(
            savedTask.assignedUser.id,
            "任务已为您创建"
        );
    }
    cb(null, savedTask);
}

上面的代码看起来干净多了,但是错误处理还是存在不足。

进行异步调用时,在执行 promise 期间可能会发生某些事情(如数据库连接错误、数据库模型验证错误等)。由于 async 函数正在等待 Promise,因此当 Promise 遇到错误时,它会抛出一个异常,该异常将在 Promisecatch 方法中被捕获。

async/await 函数中,通常使用 try/catch 块来捕获此类错误。

使用 try/catch 后代码如下:

async function asyncTasks(cb) {
    try {
        const user = await UserModel.findById(1);
        if (!user) return cb("用户未找到");
    } catch (error) {
        return cb("程序异常:可能是数据库问题");
    }
    try {
        const savedTask = await TaskModel({
            userId: user.id,
            name: "DevPoint",
        });
    } catch (error) {
        return cb("程序异常:任务保存失败");
    }
    if (user.notificationsEnabled) {
        try {
            await NotificationService.sendNotification(user.id, "任务已创建");
        } catch (error) {
            return cb("程序异常:sendNotification 失败");
        }
    }
    if (savedTask.assignedUser.id !== user.id) {
        try {
            await NotificationService.sendNotification(
                savedTask.assignedUser.id,
                "任务已为您创建"
            );
        } catch (error) {
            return cb("程序异常:sendNotification 失败");
        }
    }
    cb(null, savedTask);
}

个人对 try/catch 的使用不太喜欢,总觉得这种代码的使用是用于捕获无法预知的错误,比较喜欢 Go 的处理方式,当然这纯属个人观点。


优化 try/catch


在 Go 中的处理方式如下:

data, err := db.Query("SELECT ...")
if err != nil { return err }

它比使用 try/catch 块更干净,并且更少地聚集代码,可读和可维护性更高。

但是 await 的问题在于,如果没有为其提供 try/catch 块,它会静默退出函数。除非提供 catch 子句,否则将无法控制它。

利用 await 是在等待 resolvepromise。下面可以制作小的效用函数来捕获这些错误:

function to(promise) {
    return promise
        .then((data) => {
            return [null, data];
        })
        .catch((err) => [err]);
}

效用函数接收一个 promise,然后将成功响应解析为一个数组,并将返回数据作为第二项,并且从捕获中收到的错误是第一个。

function to(promise) {
    return promise
        .then((data) => {
            return [null, data];
        })
        .catch((err) => [err]);
}
async function asyncTask() {
    let err, user, savedTask;
    [err, user] = await to(UserModel.findById(1));
    if (!user) throw new CustomerError("用户未找到");
    [err, savedTask] = await to(
        TaskModel({ userId: user.id, name: "DevPoint" })
    );
    if (err) throw new CustomError("程序异常:任务保存失败");
    if (user.notificationsEnabled) {
        const [err] = await to(
            NotificationService.sendNotification(user.id, "任务已创建")
        );
        if (err) console.error("程序异常");
    }
}

上面的示例只是该解决方案的一个简单用例,还可以在 to 方法中增加一个拦截器,接收原始错误对象,记录它或在将它传回之前做任何需要做的事情。


总结


本文介绍了 async/await 错误处理的另一种方式,不应该将其视为标准处理方式,因为在很多情况下有一个 try/catch 块就可以了。

相关文章
|
5天前
|
SQL 安全 数据库连接
《Go 简易速速上手小册》第6章:错误处理和测试(2024 最新版)(上)
《Go 简易速速上手小册》第6章:错误处理和测试(2024 最新版)
31 1
|
2月前
|
Go 开发者
Go语言中的错误处理与异常机制:实践与最佳策略
【2月更文挑战第7天】Go语言以其独特的错误处理机制而闻名,它鼓励显式错误检查而不是依赖于异常。本文将探讨错误处理与异常机制在Go语言中的实际应用,并分享一些最佳实践,帮助开发者编写更加健壮和易于维护的Go代码。
|
1月前
|
前端开发 JavaScript 开发者
JavaScript 中的异步编程:Promise 和 Async/Await
在现代的 JavaScript 开发中,异步编程是至关重要的。本文将介绍 JavaScript 中的异步编程概念,重点讨论 Promise 和 Async/Await 这两种常见的处理异步操作的方法。通过本文的阐述,读者将能够更好地理解和应用这些技术,提高自己在 JavaScript 开发中处理异步任务的能力。
|
2天前
|
前端开发 JavaScript 编译器
深入解析JavaScript中的异步编程:Promises与async/await的使用与原理
【4月更文挑战第22天】本文深入解析JavaScript异步编程,重点讨论Promises和async/await。Promises用于管理异步操作,有pending、fulfilled和rejected三种状态。通过.then()和.catch()处理结果,但可能导致回调地狱。async/await是ES2017的语法糖,使异步编程更直观,类似同步代码,通过事件循环和微任务队列实现。两者各有优势,适用于不同场景,能有效提升代码可读性和维护性。
|
1月前
|
JavaScript 前端开发
js开发:请解释什么是ES6的async/await,以及它如何解决回调地狱问题。
ES6的`async/await`是基于Promise的异步编程工具,能以同步风格编写异步代码,提高代码可读性。它缓解了回调地狱问题,通过将异步操作封装为Promise,避免回调嵌套。错误处理更直观,类似同步的try...catch。
|
2月前
|
前端开发 JavaScript API
JavaScript学习笔记(一)promise与async
JavaScript学习笔记(一)promise与async
|
2月前
|
Go 开发者
Go语言错误处理机制:原理与实践
【2月更文挑战第7天】在Go语言中,错误处理是一项核心特性。Go语言鼓励显式的错误检查,而不是依赖于异常机制。本文将深入探讨Go语言的错误处理机制,包括错误的表示、传播和处理方式,以及如何在日常编程中有效地应用这一机制,确保代码的健壮性和可读性。
34 10
|
2月前
|
Go 开发者 UED
Go错误处理方式真的不好吗?
Go错误处理方式真的不好吗?
19 0
|
2月前
|
前端开发 JavaScript
JavaScript 中的异步编程:Promise 和 Async/Await 的实现与应用
在Web开发中,JavaScript异步编程是一个不可忽视的重要话题。本文将深入探讨JavaScript中Promise和Async/Await的实现原理与应用场景,通过实例代码带您了解如何优雅地处理异步操作,提升代码的可读性和可维护性。
|
2月前
|
前端开发 JavaScript API
一盏茶的功夫帮你彻底搞懂JavaScript异步编程从回调地狱到async/await
在深入讨论 async/await 之前,我们需要了解一下 JavaScript 的单线程和非阻塞的特性。JavaScript 是单线程的,也就是说在任何给定的时间点,只能执行一个操作。然而,对于需要大量时间的操作(例如从服务器获取数据),如果没有适当的管理机制,这种单线程特性可能会导致应用程序的阻塞。为了解决这个问题,JavaScript 引入了回调函数和后来的 Promise,用来管理这些异步操作。
http://www.vxiaotou.com