Skip to content

Allow renderActions to return promises and use that to better handle retries - #1540

Open
dpvc wants to merge 2 commits into
developfrom
feature/promise-actions
Open

Allow renderActions to return promises and use that to better handle retries#1540
dpvc wants to merge 2 commits into
developfrom
feature/promise-actions

Conversation

@dpvc

@dpvc dpvc commented Aug 5, 2026

Copy link
Copy Markdown
Member

The current renderActions list runs synchronously, but can throw a retry error when an asynchronous action is needed, and that error can be caught by handleRetriesFor() to rerun the render-action list after the asynchronous action is complete. The various entries in the list each have a flag in the document's process-bits object that they use to tell if they have already been run or not, and they skip their action if they have been run. When one that hasn't completed is reached, it runs again, usually running a command on each of the math items on the page. Each math item marks its own state to tell when its action has been performed. That way, when a later math item causes a retry to occur, the math items that have already been handled can be skipped.

That all works, but is a bit complicated, and there is a performance hit for handling the retries when you have to loop through the actions again and through the already processed math items for a suspended action. It would be nice to be able to use promises to make this work more efficiently.

This PR introduces a backward-compatible way to accomplish that. The retryAfter() function can now include a function that is called when the promise is complete, so that the render actions can be suspended and then restarted at the point where they left off, rather than running through the entire action list and beginning of the math list again. The handleRetriesFor() function uses these restart functions when they exist, or re-runs the complete code that it received, as usual, when they don't. That means that existing code will continue to work without change, but can take advantage of the new promise-based restarts that have been added into the compile and typeset functions (which are the main ones that cause restarts), and new user-defined render actions can use promises to handle tasks that need them. (I have wished for this several times in the past.)


Details

The most critical changes are in the util/Retries.ts file (at the bottom of the diff). Here, the RetryError adds a new code field that includes the code to call in order to restart the process where it left off. This is supplies as part of the retryAfter() call.

The handleRetriesFor() function now works as follows (it may be easier to view this file directly rather than use the diff, it has been heavily revised):

Wait for the user code to run and return its value
If there was an error,
   If it was a retry error,
     Restart with the code given in the retry error, or the original code,
   Otherwise fail with the error.
Continue to do this until the user code runs successfully.

This way, the actions can restart where they left off without having to go back and rerun earlier actions, even when there are multiple retries required.

The other critical changes are in core/MathDocument.ts, where the render-action processing is changed to handle return values that are promises, and where the compile and typeset actions have been refactored to be able to restart in the middle of the math list without having to go back and skip any earlier math items that were already processed.

We define a new RenderResult type for convenience, and simplify the function that creates the render actions. The renderDoc() and renderMath() (used to process a complete document, or a single math item) both get a new parameter, i, that is the index into the render-action list of where to start, and after calling the action, if it returns a promise, we do a retry that continues the process from that point on.

The renderConvert() function gets a similar parameter and handles promises like above. It also now returns the math typesetRoot or root, depending on which is is defined.

The compile() and typeset() methods are refactored so that they can be restarted using the new promise approach. The main work is done by the compileAction() and typesetAction() functions, which are the ones used by the renderDoc() functions. The renderMath() function uses the original compile() and typeset() functions, passing them false as their parameter. That is used in the action functions to determine if a restart promise should be used (when only compiling or typesetting outside of the render-action list, the original behavior is used).

A try-catch structure is used to check if the compileMath() call returns a promise, and if we are in a render action and the error is a retry error, we add a then to the promise that finishes the compile or typeset action. The handling of recompiled expressions (due to references to labels that aren't defined yet) is factored out into a separate function, since that must also handle promise return values.

In core/MathItem.ts, the compile() function returns the renderConvert() value, which will be the typeset root or the MathML root.

The core/MathList.ts file now exports a MathList type for convenience.

The util/LinkedList.ts file now adds isEnd and first() functions, for convenience.

Finally, a number of new tests are added to the Retries.test.js to test the new functionality, and to simplify existing tests using async and await rather than .then().

@codecov

codecov Bot commented Aug 5, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 72.46377% with 57 lines in your changes missing coverage. Please review.
✅ Project coverage is 86.90%. Comparing base (a8088bb) to head (b233ef0).

Files with missing lines Patch % Lines
ts/core/MathDocument.ts 62.74% 57 Missing ⚠️
Additional details and impacted files
@@             Coverage Diff             @@
##           develop    #1540      +/-   ##
===========================================
- Coverage    86.93%   86.90%   -0.03%     
===========================================
  Files          388      388              
  Lines        87571    87680     +109     
  Branches      3290     4978    +1688     
===========================================
+ Hits         76129    76201      +72     
- Misses       11442    11459      +17     
- Partials         0       20      +20     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@dpvc
dpvc requested a review from zorkow August 5, 2026 23:05
@dpvc

dpvc commented Aug 5, 2026

Copy link
Copy Markdown
Member Author

I accidentally created the PR without the initial message. I have edited it in above.

@dpvc dpvc added this to the v4.2 milestone Aug 5, 2026

@zorkow zorkow left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Three jsdoc issues. Otherwise fine.

Comment thread ts/util/LinkedList.ts
*/
public prev: ListItem<DataClass> = null;

public get isEnd() {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing jsdoc.

Comment thread ts/util/LinkedList.ts
return a < b;
}

public first(): ListItem<DataClass> {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing jsdoc.

Comment thread ts/core/MathItem.ts
*/
public convert(document: MathDocument<N, T, D>, end: number = STATE.LAST) {
document.renderActions.renderConvert(this, document, end);
return document.renderActions.renderConvert(this, document, end);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will now require a returns statement in the overridden jsdoc, i.e, in the MathItem interface.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably also needs the return type.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants