
prisma client extensions 提供了一种强大的机制,允许开发者扩展 prisma client 的功能,例如为模型添加自定义字段或方法。其中,result 扩展允许我们为模型定义“计算字段”(computed fields),这些字段的值是基于现有模型数据动态生成的。
例如,我们可以为一个 User 模型添加一个 nameAndAge 字段,它结合了用户的 name 和 age 属性:
import { PrismaClient } from "@prisma/client";
const prisma = new PrismaClient().$extends({
result: {
user: {
nameAndAge: {
needs: { name: true, age: true },
compute(user) {
return `${user.name} (${user.age}y)`;
},
},
},
},
});
async function main() {
const user = await prisma.user.findFirst();
console.log(user?.nameAndAge); // 例如: Sonia Lomo (25y)
}
main();然而,当 compute 函数需要执行异步操作时,问题就出现了。例如,如果 nameAndAge 的计算需要从外部服务获取数据:
async function getExternalData(): Promise<string> {
return " [外部数据]";
}
const prisma = new PrismaClient().$extends({
result: {
user: {
nameAndAge: {
needs: { name: true, age: true },
compute(user) {
// 直接调用异步函数
return `${user.name} (${user.age}y)` + getExternalData();
},
},
},
},
});
async function main() {
const user = await prisma.user.findFirst();
console.log(user?.nameAndAge); // 输出: Sonia Lomo (25y)[object Promise]
}
main();上述代码中,getExternalData() 返回一个 Promise,但 compute 函数是同步执行的,它不会等待 Promise 解析,而是直接将其作为字符串的一部分拼接,导致输出 [object Promise]。即使尝试将 compute 函数声明为 async 并 await 异步调用,结果也只是一个处于 pending 状态的 Promise:
// 尝试将 compute 声明为 async
async compute(user) {
return `${user.name} (${user.age}y)` + await getExternalData();
},
// ...
// console.log(user?.nameAndAge); // 输出: Promise { <pending> }出现这种行为的原因在于 Prisma 的设计理念:result 扩展的 compute 函数旨在提供同步计算的字段,以最小的开销在模型访问时进行计算,而非在数据检索时。官方文档也明确指出:“出于性能考虑,Prisma Client 在访问时计算结果,而非在检索时。”这意味着 compute 函数本身不能是异步的,也不能直接等待异步操作。
那么,如何在 Prisma Client Extensions 中优雅地处理异步计算字段呢?下面介绍两种主要的解决方案。
鉴于 compute 函数必须是同步的,一种巧妙的解决方案是让它返回一个异步函数。这个异步函数封装了所有需要执行的异步逻辑,并且可以在实际需要计算字段值时被调用并 await。
这种方法将异步计算的责任从 compute 函数本身转移到了字段的实际访问点。
import { PrismaClient } from "@prisma/client";
async function getExternalData(): Promise<string> {
// 模拟异步操作,例如网络请求或文件读取
return new Promise(resolve => setTimeout(() => resolve(" [外部数据]"), 50));
}
const prisma = new PrismaClient().$extends({
result: {
user: {
// 字段名可以改为动词形式,暗示它是一个可调用的方法
getNameAndAgeWithExternalData: {
needs: { name: true, age: true },
compute(user) {
// compute 函数同步返回一个异步函数
return async () => (`${user.name} (${user.age}y)${await getExternalData()}`);
},
},
},
},
});
async function main() {
const users = await prisma.user.findMany();
if (users.length > 0) {
// 当需要获取计算字段的值时,调用并 await 返回的异步函数
const firstUserNameAndAge = await users[0].getNameAndAgeWithExternalData();
console.log(firstUserNameAndAge); // 例如: Sonia Lomo (25y) [外部数据]
}
}
main();优点:
注意事项:
对于更复杂的场景,例如需要在查询多个记录时统一处理异步数据,或者需要对数据进行更深度的操作,model 扩展提供了一个更强大的解决方案。model 扩展允许我们为特定的模型定义自定义的查询方法,这些方法可以包含任意的异步逻辑。
通过 model 扩展,我们可以定义一个全新的查询方法,它首先执行标准的 Prisma 查询,然后对查询结果进行迭代,并为每个记录添加异步计算的字段。
import { PrismaClient, Prisma } from "@prisma/client";
async function getExternalData(): Promise<string> {
// 模拟异步操作
return new Promise(resolve => setTimeout(() => resolve(" [外部数据]"), 50));
}
// 定义一个类型,用于自定义查询方法的参数
export type UserFindManyWithComputedDataArgs = {
where?: Prisma.UserWhereInput;
select?: Prisma.UserSelect;
};
const prisma = new PrismaClient().$extends({
model: {
user: {
/**
* 查找用户并为每个用户添加异步计算的 nameAndAge 字段。
* 注意:此方法会修改返回的用户对象,添加一个非Prisma模型定义的字段。
*/
async findManyWithComputedData({ where, select }: UserFindManyWithComputedDataArgs) {
// 1. 执行标准的 Prisma 查询
const users = await prisma.user.findMany({ where, select });
// 2. 遍历查询结果,为每个用户添加异步计算的字段
// 注意:在循环中 await 可能会导致性能问题,尤其是在处理大量数据时。
// 更好的策略是使用 Promise.all 并行处理异步操作。
for (const user of users) {
// 假设 user 对象是可扩展的,或者我们将其转换为一个可扩展的类型
(user as any).nameAndAgeWithExternalData = `${user.name} (${user.age}y)${await getExternalData()}`;
}
return users;
},
/**
* 优化版本:使用 Promise.all 并行处理异步数据
*/
async findManyWithComputedDataOptimized({ where, select }: UserFindManyWithComputedDataArgs) {
const users = await prisma.user.findMany({ where, select });
const computedDataPromises = users.map(async user => {
const external = await getExternalData();
return {
...user,
nameAndAgeWithExternalData: `${user.name} (${user.age}y)${external}`
};
});
return Promise.all(computedDataPromises);
}
},
},
});
async function main() {
// 使用自定义的 model 扩展方法
const usersWithData = await prisma.user.findManyWithComputedData({});
if (usersWithData.length > 0) {
console.log(usersWithData[0].nameAndAgeWithExternalData); // 例如: Sonia Lomo (25y) [外部数据]
}
const usersWithDataOptimized = await prisma.user.findManyWithComputedDataOptimized({});
if (usersWithDataOptimized.length > 0) {
console.log(usersWithDataOptimized[0].nameAndAgeWithExternalData); // 例如: Sonia Lomo (25y) [外部数据]
}
}
main();优点:
注意事项:
Prisma Client Extensions 在处理异步计算字段时,需要我们理解其 result 扩展的同步特性。直接在 compute 函数中 await 异步操作是不可行的。
在选择合适的方案时,务必权衡代码的简洁性、性能需求以及类型安全。理解 Prisma Client Extensions 的设计哲学,能够帮助我们更有效地利用其功能,构建健壮且高性能的应用程序。
以上就是Prisma Client Extensions中处理异步计算字段的策略与实践的详细内容,更多请关注php中文网其它相关文章!
每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号