0

0

TypeScript中将SQLite数据反序列化为对象:异步处理与数据映射指南

花韻仙語

花韻仙語

发布时间:2025-11-14 11:59:39

|

346人浏览过

|

来源于php中文网

原创

TypeScript中将SQLite数据反序列化为对象:异步处理与数据映射指南

本文详细介绍了如何在typescript应用中将sqlite数据库查询结果高效且准确地反序列化为自定义的typescript类实例。核心内容包括理解数据库操作的异步性质并利用promise进行管理,以及掌握正确的数组迭代方法(`for...of`)来精确访问数据行中的列值,最终实现从原始数据库行到类型安全typescript对象的无缝转换。

在现代TypeScript应用中,将从数据库(如SQLite)中检索到的原始数据行映射到类型安全的TypeScript类或接口实例是常见的需求。这不仅提升了代码的可读性和可维护性,也为后续的业务逻辑处理提供了强类型保障。然而,在实际操作中,开发者可能会遇到一些挑战,尤其是在处理异步操作和正确的数据迭代方面。本教程将深入探讨这些问题,并提供一个健壮的解决方案。

1. 理解数据库操作的异步性质

大多数数据库操作,尤其是涉及I/O的查询,都是异步执行的。这意味着当你调用一个数据库方法时,它不会立即返回最终结果,而是可能在后台执行,并在操作完成后通过回调函数或Promise来通知你。

对于sqlite3库,其all()方法通常提供一个回调式API来处理查询结果。尝试以同步方式直接访问其返回值往往会导致错误或获取不到预期数据。为了更好地管理异步流并避免回调地狱,推荐使用Promise来封装这些异步操作,使其行为更符合现代JavaScript/TypeScript的异步编程范式。

原始问题代码中的同步误区:

export const GetAllObjs = (): Obj[] => {
  const query = db.prepare("SELECT * FROM ObjTable");
  const rows= query.all(); // 这里的query.all()不会直接返回数据,而是返回Statement对象
  // ... 后续代码会因为rows不包含实际数据而失败
};

在上述代码中,query.all()实际上返回的是Statement对象本身,而不是查询结果。查询结果是通过其内部的回调函数提供的。

使用Promise封装异步操作:

为了正确处理异步性,我们需要将数据库查询包装在一个Promise中。当query.all()的回调函数被触发时,我们可以在其中处理结果并解析(resolve)Promise。

import * as sqlite3 from 'sqlite3'; // 假设db是一个sqlite3.Database实例

interface Obj {
  id: number;
  name: string;
  amount: number;
}

// 假设db已经初始化
const db = new sqlite3.Database(':memory:'); 

// 示例:创建表
export const CreateObjTable = (): Promise<void> => {
  return new Promise((resolve, reject) => {
    db.run(`
      CREATE TABLE IF NOT EXISTS ObjTable
      (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        name TEXT,
        amount INTEGER
      )
    `, (err) => {
      if (err) {
        reject(err);
      } else {
        resolve();
      }
    });
  });
};

// 示例:插入数据
export const InsertObj = (name: string, amount: number): Promise<void> => {
  return new Promise((resolve, reject) => {
    db.run('INSERT INTO ObjTable (name, amount) VALUES (?, ?)', [name, amount], (err) => {
      if (err) {
        reject(err);
      } else {
        resolve();
      }
    });
  });
};

2. 正确的数据迭代与对象映射

在从数据库获取到数据行数组后,下一步是遍历这些行并将它们映射到预定义的TypeScript对象。这里需要注意两个关键点:

无限画
无限画

千库网旗下AI绘画创作平台

下载
  1. 选择正确的迭代方式: JavaScript/TypeScript提供了多种迭代数组的方法,其中for...in用于迭代对象的键(或数组的索引),而for...of则用于迭代可迭代对象的实际值。对于数组,我们通常需要迭代其元素(值),因此for...of是正确的选择。
  2. 访问列值: 数据库查询返回的每一行通常是一个包含列名作为键、列值为值的普通JavaScript对象。你可以直接通过点符号(row.columnName)来访问这些值。

原始问题代码中的迭代误区:

  // ...
  let objs = [] as Obj[];
  for(const row in rows as Obj[]){ // 错误:for...in 迭代的是索引
    // This object only contains the index number...
    // ...
    // id: row.id, // 错误:row此时是索引,不是数据对象
  }

在上述代码中,for(const row in rows)会使row变量在每次迭代中获取到数组的索引(例如 "0", "1", "2"),而不是实际的数据行对象。因此,尝试访问row.id等属性会失败,因为数字索引没有这些属性。

正确的迭代与映射方法:

使用for...of循环来遍历rows数组,并直接访问每个row对象中的列值。

export const GetAllObjs = (): Promise<Obj[]> => {
    const query = db.prepare("SELECT * FROM ObjTable");

    return new Promise((resolve, reject) => {
        let objs: Obj[] = []; // 明确类型

        query.all((err, rows: any[]) => { // `rows` 参数在回调中可用
            if (err) {
                return reject(err); // 发生错误时拒绝Promise
            }

            // 使用 for...of 循环迭代数组中的每个数据行对象
            for (const row of rows) { 
                objs.push(
                    {
                        id: row.id, 
                        name: row.name, 
                        amount: row.amount, 
                    } as Obj // 类型断言确保结构匹配
                );
            }
            resolve(objs); // 成功时解析Promise
        });
    });
};

3. 完整的解决方案示例

结合上述两点,下面是创建表、插入数据和查询数据并将其反序列化为TypeScript对象的一个完整且健壮的示例:

import * as sqlite3 from 'sqlite3';

// 定义数据模型接口
interface Obj {
  id: number;
  name: string;
  amount: number;
}

// 初始化数据库实例 (这里使用内存数据库作为示例)
const db = new sqlite3.Database(':memory:', (err) => {
  if (err) {
    console.error('Error opening database:', err.message);
  } else {
    console.log('Connected to the SQLite database.');
  }
});

/**
 * 创建 ObjTable 表
 * @returns {Promise<void>} 一个Promise,表示表创建操作的完成
 */
export const CreateObjTable = (): Promise<void> => {
  return new Promise((resolve, reject) => {
    const query = `
      CREATE TABLE IF NOT EXISTS ObjTable
      (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        name TEXT,
        amount INTEGER
      )
    `;
    db.run(query, (err) => {
      if (err) {
        console.error('Error creating table:', err.message);
        reject(err);
      } else {
        console.log('ObjTable created or already exists.');
        resolve();
      }
    });
  });
};

/**
 * 插入一个新对象到 ObjTable
 * @param {string} name 对象的名称
 * @param {number} amount 对象的数量
 * @returns {Promise<void>} 一个Promise,表示插入操作的完成
 */
export const InsertObj = (name: string, amount: number): Promise<void> => {
  return new Promise((resolve, reject) => {
    const query = `INSERT INTO ObjTable (name, amount) VALUES (?, ?)`;
    db.run(query, [name, amount], function(err) { // 注意这里使用function而不是箭头函数,以便访问this.lastID
      if (err) {
        console.error('Error inserting data:', err.message);
        reject(err);
      } else {
        console.log(`A row has been inserted with rowid ${this.lastID}`);
        resolve();
      }
    });
  });
};

/**
 * 从 ObjTable 获取所有对象并反序列化为 Obj 数组
 * @returns {Promise<Obj[]>} 一个Promise,解析为一个 Obj 数组
 */
export const GetAllObjs = (): Promise<Obj[]> => {
    return new Promise((resolve, reject) => {
        const query = db.prepare("SELECT id, name, amount FROM ObjTable");
        let objs: Obj[] = [];

        query.all((err, rows: any[]) => { // `rows` 是一个包含数据行的数组
            if (err) {
                console.error('Error fetching data:', err.message);
                // 确保在错误时调用 reject
                return reject(err); 
            }

            // 遍历每一行数据,并将其映射到 Obj 接口
            for (const row of rows) {
                objs.push({
                    id: row.id,
                    name: row.name,
                    amount: row.amount,
                } as Obj); // 使用类型断言明确类型
            }
            // 成功获取并映射数据后,解析 Promise
            resolve(objs);
        });
        query.finalize(); // 释放statement资源
    });
};

// 示例用法
(async () => {
  try {
    await CreateObjTable();
    await InsertObj('Item A', 100);
    await InsertObj('Item B', 250);
    await InsertObj('Item C', 75);

    const allObjs = await GetAllObjs();
    console.log('Fetched Objects:', allObjs);

    // 验证数据类型
    allObjs.forEach(obj => {
      console.log(`ID: ${obj.id}, Name: ${obj.name}, Amount: ${obj.amount}`);
      // obj.id 是 number 类型,obj.name 是 string 类型,obj.amount 是 number 类型
      // 可以在这里进行类型安全的进一步操作
    });

  } catch (error) {
    console.error('An error occurred during the operation:', error);
  } finally {
    // 关闭数据库连接
    db.close((err) => {
      if (err) {
        console.error('Error closing database:', err.message);
      } else {
        console.log('Database connection closed.');
      }
    });
  }
})();

4. 注意事项与最佳实践

  • 错误处理: 务必在Promise的reject回调中处理数据库操作可能产生的错误。在async/await结构中,可以使用try...catch捕获这些错误。
  • 资源管理: 对于sqlite3的Statement对象,使用完后应调用finalize()方法来释放资源,避免内存泄漏。
  • 类型安全: 尽管数据库返回的数据是any类型,但通过as Obj进行类型断言可以帮助TypeScript编译器理解数据的预期结构,从而在后续代码中提供类型检查和智能提示。然而,这要求你确保数据库返回的实际数据结构与Obj接口定义相匹配。
  • SQL注入防护: 在构建SQL查询时,始终使用参数化查询(如INSERT INTO ... VALUES (?, ?))来防止SQL注入攻击,而不是直接拼接字符串。
  • 异步/等待(Async/Await): 在实际应用中,推荐使用async/await语法来消费Promise,这能使异步代码看起来更像同步代码,提高可读性。

总结

将SQLite数据反序列化为TypeScript类实例是构建健壮、类型安全应用的关键一步。通过理解数据库操作的异步性质并利用Promise进行封装,以及掌握正确的数组迭代方法(for...of),我们可以有效地将原始数据库行转换为具有明确类型定义的应用程序对象。遵循本教程中介绍的模式和最佳实践,将有助于你构建更可靠、更易于维护的数据访问层。

热门AI工具

更多
DeepSeek
DeepSeek

幻方量化公司旗下的开源大模型平台

豆包大模型
豆包大模型

字节跳动自主研发的一系列大型语言模型

WorkBuddy
WorkBuddy

腾讯云推出的AI原生桌面智能体工作台

腾讯元宝
腾讯元宝

腾讯混元平台推出的AI助手

文心一言
文心一言

文心一言是百度开发的AI聊天机器人,通过对话可以生成各种形式的内容。

讯飞写作
讯飞写作

基于讯飞星火大模型的AI写作工具,可以快速生成新闻稿件、品宣文案、工作总结、心得体会等各种文文稿

即梦AI
即梦AI

一站式AI创作平台,免费AI图片和视频生成。

ChatGPT
ChatGPT

最最强大的AI聊天机器人程序,ChatGPT不单是聊天机器人,还能进行撰写邮件、视频脚本、文案、翻译、代码等任务。

相关专题

更多
TypeScript工程化开发与Vite构建优化实践
TypeScript工程化开发与Vite构建优化实践

本专题面向前端开发者,深入讲解 TypeScript 类型系统与大型项目结构设计方法,并结合 Vite 构建工具优化前端工程化流程。内容包括模块化设计、类型声明管理、代码分割、热更新原理以及构建性能调优。通过完整项目示例,帮助开发者提升代码可维护性与开发效率。

47

2026.02.13

TypeScript全栈项目架构与接口规范设计
TypeScript全栈项目架构与接口规范设计

本专题面向全栈开发者,系统讲解基于 TypeScript 构建前后端统一技术栈的工程化实践。内容涵盖项目分层设计、接口协议规范、类型共享机制、错误码体系设计、接口自动化生成与文档维护方案。通过完整项目示例,帮助开发者构建结构清晰、类型安全、易维护的现代全栈应用架构。

196

2026.02.25

TypeScript类型系统进阶与大型前端项目实践
TypeScript类型系统进阶与大型前端项目实践

本专题围绕 TypeScript 在大型前端项目中的应用展开,深入讲解类型系统设计与工程化开发方法。内容包括泛型与高级类型、类型推断机制、声明文件编写、模块化结构设计以及代码规范管理。通过真实项目案例分析,帮助开发者构建类型安全、结构清晰、易维护的前端工程体系,提高团队协作效率与代码质量。

35

2026.03.13

数据分析工具有哪些
数据分析工具有哪些

数据分析工具有Excel、SQL、Python、R、Tableau、Power BI、SAS、SPSS和MATLAB等。详细介绍:1、Excel,具有强大的计算和数据处理功能;2、SQL,可以进行数据查询、过滤、排序、聚合等操作;3、Python,拥有丰富的数据分析库;4、R,拥有丰富的统计分析库和图形库;5、Tableau,提供了直观易用的用户界面等等。

1134

2023.10.12

SQL中distinct的用法
SQL中distinct的用法

SQL中distinct的语法是“SELECT DISTINCT column1, column2,...,FROM table_name;”。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

340

2023.10.27

SQL中months_between使用方法
SQL中months_between使用方法

在SQL中,MONTHS_BETWEEN 是一个常见的函数,用于计算两个日期之间的月份差。想了解更多SQL的相关内容,可以阅读本专题下面的文章。

381

2024.02.23

SQL出现5120错误解决方法
SQL出现5120错误解决方法

SQL Server错误5120是由于没有足够的权限来访问或操作指定的数据库或文件引起的。想了解更多sql错误的相关内容,可以阅读本专题下面的文章。

2194

2024.03.06

sql procedure语法错误解决方法
sql procedure语法错误解决方法

sql procedure语法错误解决办法:1、仔细检查错误消息;2、检查语法规则;3、检查括号和引号;4、检查变量和参数;5、检查关键字和函数;6、逐步调试;7、参考文档和示例。想了解更多语法错误的相关内容,可以阅读本专题下面的文章。

380

2024.03.06

TypeScript类型系统进阶与大型前端项目实践
TypeScript类型系统进阶与大型前端项目实践

本专题围绕 TypeScript 在大型前端项目中的应用展开,深入讲解类型系统设计与工程化开发方法。内容包括泛型与高级类型、类型推断机制、声明文件编写、模块化结构设计以及代码规范管理。通过真实项目案例分析,帮助开发者构建类型安全、结构清晰、易维护的前端工程体系,提高团队协作效率与代码质量。

26

2026.03.13

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
React 教程
React 教程

共58课时 | 6万人学习

TypeScript 教程
TypeScript 教程

共19课时 | 3.4万人学习

Bootstrap 5教程
Bootstrap 5教程

共46课时 | 3.6万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号