当前位置: 首页 > news >正文

异步编程三剑客:回调、闭包与Promise组合实战

很好,这个问题其实考察的是 异步编程模型的过渡与组合应用
回调函数 → 闭包保存状态 → Promise/async,它们往往不是孤立使用,而是结合起来解决实际业务场景。


📌 回调函数、闭包和 Promise 常见组合应用

1. 回调 + 闭包

应用场景:保存上下文(状态),避免参数层层传递

闭包能“记住”调用时的环境,把参数存储起来,延迟到回调中使用。

function createTask(taskName) {return function(callback) {setTimeout(() => {callback(null, `任务 ${taskName} 完成`);}, 1000);};
}const taskA = createTask("A");
taskA((err, result) => {console.log(result); // 任务 A 完成
});

➡️ 闭包保存了 taskName,回调只关注结果。


2. 回调 + Promise(Promise 化)

应用场景:把传统回调 API 转换为 Promise,方便链式调用或 async/await

常用的就是 util.promisify 或自己封装。

const fs = require("fs");function readFilePromise(path) {return new Promise((resolve, reject) => {fs.readFile(path, "utf8", (err, data) => {if (err) return reject(err);resolve(data);});});
}readFilePromise("./test.txt").then(data => console.log("读取成功:", data)).catch(err => console.error("读取失败:", err));

➡️ 这里 回调风格 API → Promise,就是组合应用。


3. 闭包 + Promise

应用场景:保存执行环境,同时返回异步结果

比如我们想“记住 URL”,但请求时再触发。

function createFetcher(url) {return function() {return new Promise((resolve) => {setTimeout(() => {resolve(`已获取数据: ${url}`);}, 1000);});};
}const fetchUser = createFetcher("https://api/user/1");
fetchUser().then(console.log); // 已获取数据: https://api/user/1

➡️ 闭包保存 url,Promise 负责异步返回。


4. 回调 + 闭包 + Promise

应用场景:复杂业务逻辑封装,比如“延迟队列任务执行 + Promise 封装”

下面演示一个队列:任务函数内部用闭包保存状态,对外暴露 Promise 接口,同时兼容回调。

function createAsyncTask(taskName) {return function(callback) {return new Promise((resolve, reject) => {setTimeout(() => {const result = `${taskName} 完成`;if (callback) callback(null, result); // 回调resolve(result); // Promise}, 1000);});};
}// 使用回调
const task1 = createAsyncTask("下载");
task1((err, result) => console.log("回调:", result));// 使用 Promise
task1().then(result => console.log("Promise:", result));

➡️ 一个函数同时支持 回调 & Promise,闭包又能保存状态。


✅ 总结

  • 回调 + 闭包:保存状态,延迟执行时还能访问上下文。
  • 回调 + Promise:对旧 API 进行 Promise 化,兼容异步链式调用。
  • 闭包 + Promise:保存参数/环境,返回一个延迟执行的异步操作。
  • 三者结合:既能兼容传统回调,又能支持 Promise/async,常用于工具库封装。

要不要我给你写一个 支持回调 & Promise & async/await 三种调用方式的通用封装函数?这样可以完整演示三者组合的最佳实践。


判断字符串是否为合法 JSON(不依赖 JSON.parse) — 原理 + 代码

下面提供一个 逐字符解析(recursive-descent) 的实现,并在开头解释关键判断点和处理细节。目标不是构建整个对象,而是严格按 JSON 语法检查每个标记:花括号 {}、中括号 []、字符串引号 "、冒号 :、逗号 ,、转义、数字格式、布尔/空字面量、以及空白允许的位置等。
该实现会返回是否合法,并在不合法时给出一个尽可能精确的位置和信息,方便定位问题。


为什么要自己实现(关键规则回顾)

  • 对象的 key 必须是双引号字符串"name"),单引号不允许。
  • 字符串必须用双引号 " 包围,字符串内部允许转义:\" \\ \/ \b \f \n \r \t \uXXXX。未转义的控制字符(U+0000 ~ U+001F)不允许出现在字符串中。
  • 数字的语法严格:可选负号,整数部分要么是 0 要么以非零数字开头并跟随数字;小数部分(若有)必须有 . 后至少一位数字;指数部分(若有)为 e/E,可选 +/-,后面至少一位数字。
  • 数组的元素用逗号分隔,不能有尾随逗号([1,2,] 不合法)。
  • 对象的键值对用逗号分隔,key:value 之间必须有冒号 :,不能有尾随逗号。
  • true / false / null 是三个固定字面量(不能大小写改变)。
  • 任意位置可有空白字符(空格、制表、换行、回车)——它们在语法上无意义,应被跳过。

算法思路(摘要)

  • 使用索引 i 从头到尾读取字符串。
  • 提供 parseValue() 作为入口,根据当前字符分派到 parseObject / parseArray / parseString / parseNumber / parseLiteral
  • parseObject/parseArray 负责检查逗号、尾逗号、冒号、以及键类型(对象键必须是字符串)。
  • parseString 会严格验证转义序列(包括 \uXXXX)并拒绝未闭合或包含未经转义的控制字符的字符串。
  • parseNumber 按照 JSON 数字的正规式逐步校验(防止 011.1e 等非法形式)。
  • 解析结束后跳过尾部空白,索引需恰好到字符串末尾(否则有额外字符,非法)。

JS 实现代码

/*** validateJSON(text)* 返回 { valid: true } 或 { valid: false, error: { message, pos } }** 严格按照 JSON 语法验证,不调用 JSON.parse。*/
function validateJSON(text) {const s = String(text);const len = s.length;let i = 0;function error(msg) {return { valid: false, error: { message: msg, pos: i } };}function isWhitespace(ch) {return ch === ' ' || ch === '\t' || ch === '\n' || ch === '\r';}function skipWhitespace() {while (i < len && isWhitespace(s[i])) i++;}function isDigit(ch) {return ch >= '0' && ch <= '9';}function isDigit1to9(ch) {return ch >= '1' && ch <= '9';}function isHex(ch) {return /[0-9a-fA-F]/.test(ch);}// parseValue: dispatchfunction parseValue() {skipWhitespace();if (i >= len) return { ok: false, msg: 'Unexpected end while expecting a value' };const ch = s[i];if (ch === '{') return parseObject();if (ch === '[') return parseArray();if (ch === '"') {const ok = parseString();if (!ok.ok) return ok;return { ok: true };}if (ch === '-' || isDigit(ch)) {const ok = parseNumber();if (!ok.ok) return ok;return { ok: true };}// literals true/false/nullif (s.startsWith('true', i)) { i += 4; return { ok: true }; }if (s.startsWith('false', i)) { i += 5; return { ok: true }; }if (s.startsWith('null', i)) { i += 4; return { ok: true }; }return { ok: false, msg: `Unexpected token '${ch}' while expecting a value` };}// parseString: assumes s[i] === '"', moves i to char after closing quotefunction parseString() {if (s[i] !== '"') return { ok: false, msg: 'String must start with "' };i++; // consume opening "while (i < len) {const ch = s[i];if (ch === '"') { i++; return { ok: true }; } // closing quoteif (ch === '\\') {i++;if (i >= len) return { ok: false, msg: 'Unterminated escape sequence in string' };const esc = s[i];if (esc === 'u') {// need 4 hex digitsif (i + 4 >= len) return { ok: false, msg: 'Incomplete \\uXXXX escape (need 4 hex digits)' };for (let k = i + 1; k <= i + 4; k++) {if (!isHex(s[k])) return { ok: false, msg: 'Invalid hex digit in \\u escape' };}i += 5; // skip 'u' + 4 hex digits -> now i points to char after the 4 hex digitscontinue;} else if ('"\\/bfnrt'.includes(esc)) {i++; // valid simple escape, move past itcontinue;} else {return { ok: false, msg: `Invalid string escape '\\${esc}'` };}} else {// control chars (U+0000 through U+001F) are not allowed unescapedif (s.charCodeAt(i) <= 0x1F) return { ok: false, msg: 'Unescaped control character in string' };i++;}}return { ok: false, msg: 'Unterminated string (missing closing ")' };}// parseNumber: strict JSON number rulesfunction parseNumber() {const start = i;if (s[i] === '-') i++;if (i >= len) return { ok: false, msg: 'Invalid number - reached end prematurely' };if (s[i] === '0') {i++;// a leading zero must not be followed by a digitif (i < len && isDigit(s[i])) return { ok: false, msg: 'Leading zeros are not allowed in numbers' };} else if (isDigit1to9(s[i])) {while (i < len && isDigit(s[i])) i++;} else {return { ok: false, msg: 'Invalid number: expected digit' };}// fractionif (i < len && s[i] === '.') {i++;if (i >= len || !isDigit(s[i])) return { ok: false, msg: 'Fraction part requires at least one digit after dot' };while (i < len && isDigit(s[i])) i++;}// exponentif (i < len && (s[i] === 'e' || s[i] === 'E')) {i++;if (i < len && (s[i] === '+' || s[i] === '-')) i++;if (i >= len || !isDigit(s[i])) return { ok: false, msg: 'Exponent requires at least one digit' };while (i < len && isDigit(s[i])) i++;}// optionally validate numeric range by trying Number(), but not strictly necessary for syntax checkconst numStr = s.slice(start, i);if (!isFinite(Number(numStr))) return { ok: false, msg: 'Numeric value out of range' };return { ok: true };}// parseArray: expects s[i] === '['function parseArray() {if (s[i] !== '[') return { ok: false, msg: 'Array must start with [' };i++; // consume '['skipWhitespace();if (i < len && s[i] === ']') { i++; return { ok: true }; } // empty arraywhile (i < len) {const v = parseValue();if (!v.ok) return v;skipWhitespace();if (i >= len) return { ok: false, msg: 'Unterminated array (missing ])' };if (s[i] === ']') { i++; return { ok: true }; }if (s[i] === ',') {i++; // consume comma and expect another valueskipWhitespace();// disallow trailing comma: if next is ']' -> error (we'll catch when loop repeats)if (i < len && s[i] === ']') return { ok: false, msg: 'Trailing comma in array is not allowed' };continue;}return { ok: false, msg: `Expected ',' or ']' in array but found '${s[i]}'` };}return { ok: false, msg: 'Unterminated array (missing ])' };}// parseObject: expects s[i] === '{'function parseObject() {if (s[i] !== '{') return { ok: false, msg: 'Object must start with {' };i++; // consume '{'skipWhitespace();if (i < len && s[i] === '}') { i++; return { ok: true }; } // empty objectwhile (i < len) {skipWhitespace();if (i >= len) return { ok: false, msg: 'Unterminated object (missing })' };// key must be a stringif (s[i] !== '"') return { ok: false, msg: `Object keys must be strings enclosed in double quotes` };const keyOk = parseString();if (!keyOk.ok) return keyOk;skipWhitespace();if (i >= len || s[i] !== ':') return { ok: false, msg: `Missing ':' after object key` };i++; // consume ':'// parse valueconst valOk = parseValue();if (!valOk.ok) return valOk;skipWhitespace();if (i >= len) return { ok: false, msg: 'Unterminated object (missing })' };if (s[i] === '}') { i++; return { ok: true }; }if (s[i] === ',') {i++; // consume comma and continue to next key:valueskipWhitespace();// disallow trailing comma before closing braceif (i < len && s[i] === '}') return { ok: false, msg: 'Trailing comma in object is not allowed' };continue;}return { ok: false, msg: `Expected ',' or '}' in object but found '${s[i]}'` };}return { ok: false, msg: 'Unterminated object (missing })' };}// mainskipWhitespace();if (i >= len) return error('Empty input (no JSON value)');const root = parseValue();if (!root.ok) return error(root.msg);skipWhitespace();if (i !== len) return error('Unexpected extra characters after valid JSON value');return { valid: true };
}

使用示例(及预期结果)

const tests = ['{"a":1, "b":[true,false,null, "str", {"x": -12.34e+10}]}', // valid'  [ 1, 2, 3 ]  ', // valid'"a string with \\u0041"', // valid'123', // valid (JSON allows a top-level number)'null', // valid"{'a':1}", // invalid: single quotes'{"a":1,}', // invalid: trailing comma'["a",]', // invalid: trailing comma'{"a": "b}', // invalid: unterminated string'01', // invalid: leading zero'1e', // invalid: incomplete exponent'{ "a": true false }', // invalid: missing comma
];for (const t of tests) {const r = validateJSON(t);console.log(JSON.stringify({ input: t, result: r }));
}

预期(示例):

  • 前 5 个返回 { valid: true }
  • 后面几项会返回 { valid: false, error: { message: '...', pos: <index> } }message 会指明是单引号、尾随逗号、未闭合字符串、前导零、缺失指数数字、缺少逗号等具体原因,pos 给出发生错误时的字符位置,便于调试。

扩展建议

  • 当前实现只做语法验证。如果你还想要 同时把字符串解析成 JS 对象,可以把 parseObject / parseArray / parseString 返回实际构造的值(但会消耗更多内存)。
  • 可改进的点:返回更细粒度的错误信息(期望项、发现项、行列号),例如把 pos 转为 line:col,对大文件可加上最大递归深度限制以防栈溢出。
  • 若你仅需在 Node/浏览器里使用,JSON.parse 已足够且更快速。但当你需要给用户更好的报错定位或在受限环境验证输入时,上面的实现更有用。

如果你需要,我可以:

  • 把上面的验证器改成 同时返回解析好的 JS 值(即实现一个完整的 JSON 解析器),或者
  • 增加行列信息(line/column)以便在编辑器中高亮错误位置,或者
  • 把它改写为 生成更友好的错误消息(例如建议修复方法)。要哪个我直接给出代码即可。
    加粗样式

文章转载自:

http://htFE7S0q.yhyqg.cn
http://8A8MVHR9.yhyqg.cn
http://oOGhCUMt.yhyqg.cn
http://ebvcBOqZ.yhyqg.cn
http://WDL43g9n.yhyqg.cn
http://207nJZTa.yhyqg.cn
http://mCn8Qg07.yhyqg.cn
http://Jcv1b8N7.yhyqg.cn
http://MGeqn7xW.yhyqg.cn
http://93vUxK4n.yhyqg.cn
http://RYkpEAhM.yhyqg.cn
http://FVNiRfcC.yhyqg.cn
http://dvtV6849.yhyqg.cn
http://eBazb6Hw.yhyqg.cn
http://Nf7Cc0RH.yhyqg.cn
http://BPcr0W4g.yhyqg.cn
http://GRLyfxJn.yhyqg.cn
http://zvM3Ivh1.yhyqg.cn
http://Xcli34NN.yhyqg.cn
http://Bjise7lU.yhyqg.cn
http://qozQyW74.yhyqg.cn
http://nId0T4Pa.yhyqg.cn
http://lG0T1gao.yhyqg.cn
http://aVaQKWhv.yhyqg.cn
http://KWtrJjAH.yhyqg.cn
http://tFHUthQO.yhyqg.cn
http://auVwizk1.yhyqg.cn
http://IzycQ3pm.yhyqg.cn
http://sCPAYeA4.yhyqg.cn
http://FQTREn5g.yhyqg.cn
http://www.dtcms.com/a/385315.html

相关文章:

  • MySQL 主从同步(复制)实战
  • redis面试点记录
  • mysql和postgresql如何选择
  • 电磁兼容性(EMC)法规
  • 【论文笔记】Self-Supervised Point Cloud Prediction for Autonomous Driving
  • MySQL数据库(四)—— 使用MyCat实现MySQL主从读写分离实战指南
  • HTB paper
  • oracle认证有哪几种?如何选择
  • YoloV8改进策略:上采样改进|反卷积|数学上可逆的反卷积」塞进 YOLOv8,涨点不涨参!图像恢复黑科技 Converse2D 的跨界奇袭!
  • springboot netty 服务端网络编程入门与实战
  • 从零开始学AI——15
  • Linux C库函数的可重入与不可重入版本说明
  • ZooKeeper核心知识点总结:分布式系统的“协调者”
  • Unreal故障艺术之RGB颜色分离故障
  • 金融数据---东方财富人气榜-A股
  • 设计模式详解——创建型
  • Java 泛型与通配符全解析
  • Python变量与数据类型全解析:从命名规则到类型转换
  • 了解篇 | StarRocks 是个什么数据库?
  • 风险控制规则引擎:从敏捷开发工具到管理逻辑的承载者
  • 基于Matlab深度学习的植物叶片智能识别系统及其应用
  • AI编程从0-1开发一个小程序
  • Android原生的TextToSpeech,文字合成语音并播放
  • 【03】AI辅助编程完整的安卓二次商业实战-本地构建运行并且调试-二次开发改注册登陆按钮颜色以及整体资源结构熟悉-优雅草伊凡
  • 高德api使用
  • 工程造价指数指标分析:从数据采集到决策支撑的工程经济实践
  • 中控平台数据监控大屏
  • Vue 与 React 的区别?
  • 元图CAD:智能工程图纸解决方案的商业模型创新
  • MySQL 全量备份迁移步骤指南