为何mathjax-node的typeset()回调晚于程序终止?如何同步执行?
MathJax-Node异步回调执行顺序问题
问题现象
运行以下Node.js代码:
var mjAPI = require('mathjax-node'); mjAPI.config({ MathJax: { // traditional MathJax configuration } }); mjAPI.start(); writeLatexInfo("1","\\alpha"); writeLatexInfo("2","\\omega"); console.log(">> TERMINATED"); function writeLatexInfo(sRef, sLatex) { console.log(`<ref>${sRef}</ref><latex>${sLatex}</latex>`); mjAPI.typeset({ math: sLatex, format: "TeX", // or "inline-TeX", "MathML" mml:true, // or svg:true, or html:true }, function (data) { if (!data.errors) { console.log(data.mml); } }); }
实际输出中>> TERMINATED会早于<math>元素打印,而期望输出是每个<math>紧跟对应</latex>标签,最后输出终止信息:
<ref>1</ref><latex>\alpha</latex> <math xmlns="http://www.w3.org/1998/Math/MathML" display="block" alttext="\alpha"> <mi>α<!-- α --></mi> </math> <ref>2</ref><latex>\omega</latex> <math xmlns="http://www.w3.org/1998/Math/MathML" display="block" alttext="\omega"> <mi>ω<!-- ω --></mi> </math> >> TERMINATED
原因解析
mjAPI.typeset()是异步操作,Node.js的单线程事件循环机制会优先执行同步代码:
- 先依次执行
writeLatexInfo里的同步console.log,输出<ref><latex>标签 - 调用
mjAPI.typeset()时,会把回调函数放到异步任务队列,不阻塞当前同步代码执行 - 同步代码全部执行完毕(包括最后的
console.log(">> TERMINATED"))后,事件循环才会处理任务队列里的异步回调,所以<math>元素会在终止信息之后打印。
同步执行的解决方案
可以用Promise封装mjAPI.typeset(),结合async/await或者Promise.all来控制执行顺序:
方法1:用Promise+async/await严格按顺序执行
var mjAPI = require('mathjax-node'); mjAPI.config({ MathJax: { // traditional MathJax configuration } }); mjAPI.start(); // 把typeset封装为Promise function typesetPromise(sLatex) { return new Promise((resolve, reject) => { mjAPI.typeset({ math: sLatex, format: "TeX", mml: true }, (data) => { data.errors ? reject(data.errors) : resolve(data.mml); }); }); } // 改造writeLatexInfo为异步函数 async function writeLatexInfo(sRef, sLatex) { console.log(`<ref>${sRef}</ref><latex>${sLatex}</latex>`); const mml = await typesetPromise(sLatex); console.log(mml); } // 主流程包裹在async函数中,确保顺序执行 async function main() { await writeLatexInfo("1", "\\alpha"); await writeLatexInfo("2", "\\omega"); console.log(">> TERMINATED"); } main().catch(err => console.error(err));
方法2:用Promise.all并行处理(仍保证最终顺序)
如果不需要严格串行处理两个LaTeX,只是保证所有回调执行完再打印终止信息,可使用Promise.all:
var mjAPI = require('mathjax-node'); mjAPI.config({ MathJax: { // traditional MathJax configuration } }); mjAPI.start(); function typesetPromise(sLatex) { return new Promise((resolve, reject) => { mjAPI.typeset({ math: sLatex, format: "TeX", mml: true }, (data) => { data.errors ? reject(data.errors) : resolve(data.mml); }); }); } async function writeLatexInfo(sRef, sLatex) { console.log(`<ref>${sRef}</ref><latex>${sLatex}</latex>`); const mml = await typesetPromise(sLatex); console.log(mml); } async function main() { // 并行执行两个任务,每个任务内部的输出顺序仍对应 await Promise.all([ writeLatexInfo("1", "\\alpha"), writeLatexInfo("2", "\\omega") ]); console.log(">> TERMINATED"); } main().catch(err => console.error(err));
两种方式都能实现<math>元素紧跟对应标签打印,最后输出终止信息的需求。
内容的提问来源于stack exchange,提问作者schlebe
相关产品推荐
相关产品推荐

