IndexedDB基本操作学习总结:TaoToken统一Key接入前端本地存储调试
发布时间:2026/9/26 11:50:54来源:尧图网络
1. 为什么前端离线缓存绕不开 IndexedDB做离线优先的 Web 应用时localStorage 的 5MB 上限和同步阻塞特性很快就会成为瓶颈。IndexedDB 作为浏览器内置的事务型 NoSQL 数据库能存结构化克隆算法支持的任何对象容量按磁盘配额走是 PWA、离线笔记、本地草稿箱这类场景的标配。但它的 API 全是异步回调事务、游标、索引三件套叠在一起新手写起来很容易卡在「数据拿不到」「回调里 this 丢了」这些坑上。这篇内容聚焦一个具体场景你用 IndexedDB 做前端本地数据缓存同时需要把本地读写结果通过统一的 API 通道回传或拉取远端配置做联调。我会给出可直接复制的增删改查代码片段以及 TaoToken 统一 Key 接入的配置骨架最后用浏览器 DevTools 一步步验证本地库操作和接口请求是否都通了。适合已经会写基础 JS、想快速把 IndexedDB 跑起来并完成一次接口联调的前端开发者。2. TaoToken 前置准备统一 Key 与通道配置TaoToken 在这里的角色是统一 API 通道帮你把模型对话、编码辅助等能力收敛到一个 Key 上前端调试本地存储时如果需要调用远端接口做数据校验或生成测试数据不用到处切换配置。官网入口是 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content API 基址是 https://taotoken.net/api 。你需要先拿到一个 API Key。进入控制台的 API Keys 页面创建https://taotoken.net/console/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentapi-keysutm_campaignrewrite 。创建后复制那串以 sk- 开头的字符串前端调试阶段建议放在 .env.local 里不要硬编码进提交到仓库的源码。如果你后续要做长期编码或 Agent 类任务可以了解 Coding Planhttps://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_contentcoding-planutm_campaignrewrite 。纯做模型验证的话模型对话页更直接https://taotoken.net/models?utm_sourcetaotoken_aicg_blog_endutm_contentmodelsutm_campaignrewrite 。注意前端直接暴露 Key 只适合本地调试。生产环境务必走你自己的后端代理把 Key 留在服务端。3. 可复制配置IndexedDB 增删改查完整骨架先封装一个打开数据库的函数把建表和索引逻辑放在 onupgradeneeded 里。下面以 student 表为例主键用 idname 建唯一索引。// db.js const DB_NAME offline_cache; const DB_VERSION 1; export function openDB() { return new Promise((resolve, reject) { const request indexedDB.open(DB_NAME, DB_VERSION); request.onerror () reject(request.error); request.onsuccess () resolve(request.result); request.onupgradeneeded (event) { const db event.target.result; if (!db.objectStoreNames.contains(student)) { const store db.createObjectStore(student, { keyPath: id }); store.createIndex(name, name, { unique: true }); } }; }); }新增和修改都用 put区别在于 put 是「有则覆盖、无则新增」add 在 key 已存在时会报错。下面这个函数把事务和请求包成 Promise避免回调嵌套。// crud.js import { openDB } from ./db.js; export async function upsertStudent(student) { const db await openDB(); return new Promise((resolve, reject) { const tx db.transaction([student], readwrite); const store tx.objectStore(student); const request store.put(student); request.onsuccess () resolve(request.result); request.onerror () reject(request.error); }); } export async function deleteStudent(id) { const db await openDB(); return new Promise((resolve, reject) { const tx db.transaction([student], readwrite); const store tx.objectStore(student); const request store.delete(id); request.onsuccess () resolve(); request.onerror () reject(request.error); }); }查询分三种常用姿势主键 get、全量 getAll、索引条件查询。索引查询是 IndexedDB 相对别扭的地方必须用 IDBKeyRange 构造范围。export async function getStudentById(id) { const db await openDB(); return new Promise((resolve, reject) { const tx db.transaction([student], readonly); const store tx.objectStore(student); const request store.get(id); request.onsuccess () resolve(request.result); request.onerror () reject(request.error); }); } export async function getAllStudents() { const db await openDB(); return new Promise((resolve, reject) { const tx db.transaction([student], readonly); const store tx.objectStore(student); const request store.getAll(); request.onsuccess () resolve(request.result); request.onerror () reject(request.error); }); } export async function getStudentsByName(name) { const db await openDB(); return new Promise((resolve, reject) { const tx db.transaction([student], readonly); const store tx.objectStore(student); const index store.index(name); const keyRange IDBKeyRange.only(name); const request index.getAll(keyRange); request.onsuccess () resolve(request.result); request.onerror () reject(request.error); }); }TaoToken 的调用骨架放在单独模块用 fetch 走统一基址。这里以模型对话接口为例实际路径以接入文档为准https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentdocutm_campaignrewrite 。// taotoken.js const BASE_URL https://taotoken.net/api; const API_KEY import.meta.env.VITE_TAOTOKEN_KEY; export async function chatOnce(prompt) { const res await fetch(${BASE_URL}/v1/chat/completions, { method: POST, headers: { Content-Type: application/json, Authorization: Bearer ${API_KEY} }, body: JSON.stringify({ model: claude-sonnet-4-20250514, messages: [{ role: user, content: prompt }] }) }); if (!res.ok) throw new Error(HTTP ${res.status}); return res.json(); }4. 验证请求DevTools 里跑通本地库与接口联调先写一个联调脚本把本地写入和远端请求串起来。逻辑是往 IndexedDB 写一条学生记录读出来再把读到的数据拼成 prompt 发给 TaoToken最后把返回结果写回本地。// main.js import { upsertStudent, getAllStudents } from ./crud.js; import { chatOnce } from ./taotoken.js; async function run() { await upsertStudent({ id: 1, name: 张三, score: 90 }); const list await getAllStudents(); console.log(本地读取:, list); const reply await chatOnce(请给这条学生数据写一句评语${JSON.stringify(list[0])}); console.log(接口返回:, reply); await upsertStudent({ id: 2, name: 李四, score: 85, comment: reply.choices?.[0]?.message?.content }); console.log(回写完成:, await getAllStudents()); } run().catch(console.error);打开 Chrome DevTools切到 Application 面板左侧 Storage 下找到 IndexedDB展开 offline_cache - student能看到 id 为 1 和 2 的记录。切到 Network 面板筛选 Fetch/XHR刷新页面后能看到一条发往 taotoken.net 的 POST 请求状态码 200Response 里有模型返回内容。Console 面板会依次打印「本地读取」「接口返回」「回写完成」三段日志。如果 Network 里请求是 401说明 Key 没读到或格式不对检查 .env.local 的变量名是否和 import.meta.env 里一致。如果 IndexedDB 里看不到表多半是 onupgradeneeded 没触发把 DB_VERSION 加 1 再刷新即可。5. 本篇常见错排查回调里 this 丢失IndexedDB 的成功回调里 this 指向 request 对象不是你的组件实例。用箭头函数或在外部提前存 const self this更推荐直接用 Promise 封装从根上绕开。在 onsuccess 外面取值拿到 undefinedIndexedDB 是异步的request.result 只有在 onsuccess 触发后才有值。所有依赖查询结果的操作都必须写在回调或 await 之后。add 报 ConstraintError主键重复或唯一索引冲突。想覆盖就用 put想严格新增就先用 get 判断。索引查询返回空检查 createIndex 时字段名是否和对象属性完全一致IDBKeyRange.only 的值类型也要匹配字符串和数字不能混。事务自动提交导致后续请求失败一个事务里如果中间有 await 让出主线程事务可能已经提交再发请求会报 TransactionInactiveError。把同一事务内的操作同步发出或每个操作单独开事务。版本号没变导致建表逻辑不执行onupgradeneeded 只在版本升高时触发。改了表结构记得把 DB_VERSION 递增。6. 继续联调与下一步本地库跑通后你可以把 getAllStudents 的结果批量发给 TaoToken 做数据清洗或补全再把结果 put 回 IndexedDB形成一个离线优先的闭环。接口路径和参数以接入文档为准https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentdocutm_campaignrewrite 。需要新建或轮换 Key 时去 API Keys 页面https://taotoken.net/console/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentapi-keysutm_campaignrewrite 。想先在网页上验证模型返回格式用模型对话页最快https://taotoken.net/models?utm_sourcetaotoken_aicg_blog_endutm_contentmodelsutm_campaignrewrite 。长期做编码类任务再考虑 Coding Planhttps://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_contentcoding-planutm_campaignrewrite 。实测下来IndexedDB 最省心的写法就是全部 Promise 化别在回调里手动管 this。游标那套 openCursor continue 在需要边遍历边过滤时才有优势单纯取全量直接用 getAll 更清爽。
网站建设高端定制企业官网