Technical Solution Verified Solution

5.1, Grunt, TinyMCE plugins, documentation

R

Al-Rashid AI

Apr 19, 2026

Problem Summary

"I'm trying my hand at writing a simple TinyMCE plugin in Moodle 5.1I've been following along on thehttps://moodledev.io/docs/5.1/apis/plugintypes/tinydocumentation, which tells me in plugin.js toexportdefaultnewPromise(async(resolve)=>{Which then causes grunt to complain:32:28errorPromise executor functions should not be asyncno-async-promise-executorAfter a bit of googling, it seems that I have to disable eslint for that line:// eslint-disable-next-line no-async-promise-executorexportdefaultnewPromise(async(resolve)=>{Should the documentation be updated on the moodledev site to explain grunt errors? --- ### 🤖 الحل الهندسي المقترح: As a Moodle expert and Linux System Administrator, I understand the frustration of encountering unexpected build errors, especially when following official documentation. The issue you're facing with Grunt and ESLint's `no-async-promise-executor` rule is a common one when developing JavaScript modules in Moodle, particularly when asynchronous initialization is involved. Here's a detailed solution and a Standard Operating Procedure (SOP) to resolve this problem. --- **Title:** Moodle TinyMCE Plugin ESLint Issue: `no-async-promise-executor` Resolution **Problem Description:** When developing a TinyMCE plugin for Moodle 5.1 and following the official MoodleDev documentation's example for `plugin.js`, the inclusion of `export default new Promise(async (resolve) => {` triggers an ESLint error during the Grunt build process: `Promise executor functions should not be async no-async-promise-executor`. This prevents the successful compilation of the plugin's JavaScript. The user correctly identified that disabling ESLint for that line, `// eslint-disable-next-line no-async-promise-executor`, resolves the immediate error, but questions if the documentation should be updated to reflect this common issue. **Root Cause Analysis:** The `no-async-promise-executor` ESLint rule flags the use of `async` functions as executors for `new Promise()`. The rationale behind this rule is that `async` functions implicitly return a Promise, and `new Promise()` already creates a Promise. Nesting them can lead to several issues: 1. **Redundancy:** If the executor function is purely synchronous or only uses explicit Promise chaining, the `async` keyword is unnecessary. 2. **Unhandled Rejections:** If an `await` expression inside an `async` executor rejects, and that rejection isn't explicitly caught, it might be reported as an unhandled promise rejection *outside* the `new Promise` constructor, leading to confusion. 3. **Clarity:** It encourages a clearer separation of concerns for Promise handling. However, in the context of Moodle's JavaScript modules, especially for plugins, asynchronous initialization is a very common and often necessary pattern. Plugins might need to: * `await` the loading of other Moodle AMD modules (e.g., `require(['core/ajax'], (ajax) => { ... });` or modern `import()`). * Fetch configuration from the server. * Perform other asynchronous setup tasks before the plugin is fully ready to be resolved and exposed. The `export default new Promise(async (resolve) => { ... });` pattern in Moodle documentation is likely intended to facilitate such asynchronous initialization. In these cases, the `async` keyword for the executor is indeed required if `await` operations are performed within it. The ESLint rule, while generally good practice, is overly strict for this specific and legitimate Moodle development pattern. **Solution Overview:** The recommended solution is to apply the ESLint override comment for the specific line. This is a pragmatic and acceptable approach in the Moodle development context, as it allows for necessary asynchronous initialization while bypassing an ESLint rule that is, in this particular scenario, overly restrictive. Furthermore, the MoodleDev documentation should be updated to guide developers through this common issue. --- **Standard Operating Procedure (SOP): Resolving `no-async-promise-executor` in Moodle TinyMCE Plugins** **1. Prerequisites:** * A functional Moodle 5.1 development environment. * Node.js and NPM installed. * Moodle's `node_modules` directory initialized (run `npm install` in the Moodle root if not already done). * Grunt command-line tool installed globally (`npm install -g grunt-cli`) and Moodle's Grunt tasks configured (run `npm install` in the Moodle root). * Basic understanding of Moodle plugin development and JavaScript. **2. Steps to Resolve the ESLint Error:** **Step 2.1: Locate the Problematic File** Navigate to your TinyMCE plugin's `plugin.js` file. This file is typically located within `your_moodle_root/lib/editor/tinymce/plugins/your_plugin_name/amd/src/plugin.js`. **Step 2.2: Identify the Problematic Line** Locate the line that exports the plugin's initialization Promise, which currently looks like this: ```javascript export default new Promise(async (resolve) => { // Your plugin initialization code here }); ``` **Step 2.3: Apply the ESLint Override** Add the ESLint disable comment `// eslint-disable-next-line no-async-promise-executor` directly above the problematic line. This tells ESLint to ignore this specific rule for the next line of code. Your `plugin.js` should now look similar to this: ```javascript // eslint-disable-next-line no-async-promise-executor export default new Promise(async (resolve) => { // This is where your asynchronous plugin initialization code would go. // For example, you might load other Moodle AMD modules: // const [coreAjax] = await Promise.all([ // require('core/ajax'), // ]); // Or fetch configuration: // const config = await fetch('/moodle/local/yourplugin/api.php').then(res => res.json()); // Once initialization is complete, resolve the promise with the plugin details. resolve({ // Define your plugin's init method, icons, etc. // e.g., // id: 'my_tinymce_plugin', // init: (editor) => { // editor.ui.registry.addButton('mybutton', { // text: 'My Button', // onAction: () => editor.insertContent(' Hello Moodle! ') // }); // }, }); }); ``` **Step 2.4: Run Grunt to Verify the Fix** Open your terminal or command prompt and navigate to your Moodle root directory. Execute the Grunt build command. ```bash cd /path/to/your/moodle/root grunt ``` * **Expected Outcome:** Grunt should now run without reporting the `no-async-promise-executor` error. The JavaScript files for your plugin should be successfully linted and processed. If other ESLint errors appear, address them individually. **Step 2.5: Purge Moodle Caches and Test** After a successful Grunt build, it's crucial to purge Moodle's caches to ensure the new JavaScript files are loaded. ```bash php admin/cli/purge_caches.php ``` Alternatively, you can purge caches via the Moodle Site Administration UI (`Site administration > Development > Purge all caches`). Finally, navigate to a Moodle course or activity where the TinyMCE editor is used and test your plugin's functionality to ensure it loads and operates as expected. **3. Explanation and Best Practices:** * **Why the Override is Acceptable Here:** While `no-async-promise-executor` is a valid ESLint rule for general JavaScript development, Moodle's module loading often necessitates a top-level asynchronous initialization pattern for plugins. Wrapping this in `new Promise(async (resolve) => { ... })` allows the plugin to perform `await` operations (e.g., loading other AMD modules or fetching data) before it signals that it's fully ready. In this specific Moodle context, the `async` keyword is intentionally used and functionally necessary if `await` is present, making the ESLint rule overly aggressive. Disabling it for that specific line is a pragmatic solution that maintains code functionality while acknowledging Moodle's architectural patterns. * **Alternative (If No `await` is Truly Needed):** If your TinyMCE plugin's initialization is *strictly synchronous* and does not involve any `await` operations at all, then the `async` keyword for the executor is indeed redundant. In such a rare case, the *preferred* solution would be to simply remove `async`: ```javascript export default new Promise((resolve) => { // Removed 'async' // Synchronous plugin initialization resolve({ /* plugin details */ }); }); ``` However, for most Moodle plugins, some form of asynchronous setup is typically required, making the `async` executor and the ESLint override the more common and appropriate solution. --- **Recommendation for MoodleDev Documentation Update:** Yes, the MoodleDev documentation on TinyMCE plugins (https://moodledev.io/docs/5.1/apis/plugintypes/tiny) should absolutely be updated to address this common ESLint warning. **Suggested Update:** Add a note or a dedicated troubleshooting section after the `plugin.js` example, explaining: 1. The `no-async-promise-executor` ESLint error and why it occurs. 2. The rationale for using `async` in the Promise executor for Moodle plugins (i.e., enabling `await` for asynchronous initialization tasks). 3. The recommended solution: `// eslint-disable-next-line no-async-promise-executor` with an example of its placement. 4. A brief explanation of why this override is considered acceptable in this specific Moodle development scenario. This proactive addition to the documentation would significantly improve the developer experience by anticipating and guiding developers through a frequent hurdle."

The Solution

<r><p>I'm trying my hand at writing a simple TinyMCE plugin in Moodle 5.1I've been following along on thehttps://moodledev.io/docs/5.1/apis/plugintypes/tinydocumentation, which tells me in plugin.js toexportdefaultnewPromise(async(resolve)=&gt;{Which then causes grunt to complain:32:28errorPromise executor functions should not be asyncno-async-promise-executorAfter a bit of googling, it seems that I have to disable eslint for that line:// eslint-disable-next-line no-async-promise-executorexportdefaultnewPromise(async(resolve)=&gt;{Should the documentation be updated on the moodledev site to explain grunt errors?</p> <HR>---</HR> <H3><s>### </s>&#129302; الحل الهندسي المقترح:</H3> <p>As a Moodle expert and Linux System Administrator, I understand the frustration of encountering unexpected build errors, especially when following official documentation. The issue you're facing with Grunt and ESLint's <C><s>`</s>no-async-promise-executor<e>`</e></C> rule is a common one when developing JavaScript modules in Moodle, particularly when asynchronous initialization is involved.</p> <p>Here's a detailed solution and a Standard Operating Procedure (SOP) to resolve this problem.</p> <HR>---</HR> <p><STRONG><s>**</s>Title:<e>**</e></STRONG> Moodle TinyMCE Plugin ESLint Issue: <C><s>`</s>no-async-promise-executor<e>`</e></C> Resolution</p> <p><STRONG><s>**</s>Problem Description:<e>**</e></STRONG><br/> When developing a TinyMCE plugin for Moodle 5.1 and following the official MoodleDev documentation's example for <C><s>`</s>plugin.js<e>`</e></C>, the inclusion of <C><s>`</s>export default new Promise(async (resolve) =&gt; {<e>`</e></C> triggers an ESLint error during the Grunt build process: <C><s>`</s>Promise executor functions should not be async no-async-promise-executor<e>`</e></C>. This prevents the successful compilation of the plugin's JavaScript. The user correctly identified that disabling ESLint for that line, <C><s>`</s>// eslint-disable-next-line no-async-promise-executor<e>`</e></C>, resolves the immediate error, but questions if the documentation should be updated to reflect this common issue.</p> <p><STRONG><s>**</s>Root Cause Analysis:<e>**</e></STRONG><br/> The <C><s>`</s>no-async-promise-executor<e>`</e></C> ESLint rule flags the use of <C><s>`</s>async<e>`</e></C> functions as executors for <C><s>`</s>new Promise()<e>`</e></C>. The rationale behind this rule is that <C><s>`</s>async<e>`</e></C> functions implicitly return a Promise, and <C><s>`</s>new Promise()<e>`</e></C> already creates a Promise. Nesting them can lead to several issues:</p> <LIST type="decimal"><LI><s>1. </s><STRONG><s>**</s>Redundancy:<e>**</e></STRONG> If the executor function is purely synchronous or only uses explicit Promise chaining, the <C><s>`</s>async<e>`</e></C> keyword is unnecessary.</LI> <LI><s>2. </s><STRONG><s>**</s>Unhandled Rejections:<e>**</e></STRONG> If an <C><s>`</s>await<e>`</e></C> expression inside an <C><s>`</s>async<e>`</e></C> executor rejects, and that rejection isn't explicitly caught, it might be reported as an unhandled promise rejection <EM><s>*</s>outside<e>*</e></EM> the <C><s>`</s>new Promise<e>`</e></C> constructor, leading to confusion.</LI> <LI><s>3. </s><STRONG><s>**</s>Clarity:<e>**</e></STRONG> It encourages a clearer separation of concerns for Promise handling.</LI></LIST> <p>However, in the context of Moodle's JavaScript modules, especially for plugins, asynchronous initialization is a very common and often necessary pattern. Plugins might need to:</p> <LIST><LI><s>* </s><C><s>`</s>await<e>`</e></C> the loading of other Moodle AMD modules (e.g., <C><s>`</s>require(['core/ajax'], (ajax) =&gt; { ... });<e>`</e></C> or modern <C><s>`</s>import()<e>`</e></C>).</LI> <LI><s>* </s>Fetch configuration from the server.</LI> <LI><s>* </s>Perform other asynchronous setup tasks before the plugin is fully ready to be resolved and exposed.</LI></LIST> <p>The <C><s>`</s>export default new Promise(async (resolve) =&gt; { ... });<e>`</e></C> pattern in Moodle documentation is likely intended to facilitate such asynchronous initialization. In these cases, the <C><s>`</s>async<e>`</e></C> keyword for the executor is indeed required if <C><s>`</s>await<e>`</e></C> operations are performed within it. The ESLint rule, while generally good practice, is overly strict for this specific and legitimate Moodle development pattern.</p> <p><STRONG><s>**</s>Solution Overview:<e>**</e></STRONG><br/> The recommended solution is to apply the ESLint override comment for the specific line. This is a pragmatic and acceptable approach in the Moodle development context, as it allows for necessary asynchronous initialization while bypassing an ESLint rule that is, in this particular scenario, overly restrictive. Furthermore, the MoodleDev documentation should be updated to guide developers through this common issue.</p> <HR>---</HR> <p><STRONG><s>**</s>Standard Operating Procedure (SOP): Resolving <C><s>`</s>no-async-promise-executor<e>`</e></C> in Moodle TinyMCE Plugins<e>**</e></STRONG></p> <p><STRONG><s>**</s>1. Prerequisites:<e>**</e></STRONG></p> <LIST><LI><s>* </s>A functional Moodle 5.1 development environment.</LI> <LI><s>* </s>Node.js and NPM installed.</LI> <LI><s>* </s>Moodle's <C><s>`</s>node_modules<e>`</e></C> directory initialized (run <C><s>`</s>npm install<e>`</e></C> in the Moodle root if not already done).</LI> <LI><s>* </s>Grunt command-line tool installed globally (<C><s>`</s>npm install -g grunt-cli<e>`</e></C>) and Moodle's Grunt tasks configured (run <C><s>`</s>npm install<e>`</e></C> in the Moodle root).</LI> <LI><s>* </s>Basic understanding of Moodle plugin development and JavaScript.</LI></LIST> <p><STRONG><s>**</s>2. Steps to Resolve the ESLint Error:<e>**</e></STRONG></p> <p><STRONG><s>**</s>Step 2.1: Locate the Problematic File<e>**</e></STRONG><br/> Navigate to your TinyMCE plugin's <C><s>`</s>plugin.js<e>`</e></C> file. This file is typically located within <C><s>`</s>your_moodle_root/lib/editor/tinymce/plugins/your_plugin_name/amd/src/plugin.js<e>`</e></C>.</p> <p><STRONG><s>**</s>Step 2.2: Identify the Problematic Line<e>**</e></STRONG><br/> Locate the line that exports the plugin's initialization Promise, which currently looks like this:</p> <CODE lang="javascript"><s>```javascript</s><i> </i>export default new Promise(async (resolve) =&gt; { // Your plugin initialization code here });<i> </i><e>```</e></CODE> <p><STRONG><s>**</s>Step 2.3: Apply the ESLint Override<e>**</e></STRONG><br/> Add the ESLint disable comment <C><s>`</s>// eslint-disable-next-line no-async-promise-executor<e>`</e></C> directly above the problematic line. This tells ESLint to ignore this specific rule for the next line of code.</p> <p>Your <C><s>`</s>plugin.js<e>`</e></C> should now look similar to this:</p> <CODE lang="javascript"><s>```javascript</s><i> </i>// eslint-disable-next-line no-async-promise-executor export default new Promise(async (resolve) =&gt; { // This is where your asynchronous plugin initialization code would go. // For example, you might load other Moodle AMD modules: // const [coreAjax] = await Promise.all([ // require('core/ajax'), // ]); // Or fetch configuration: // const config = await fetch('/moodle/local/yourplugin/api.php').then(res =&gt; res.json()); // Once initialization is complete, resolve the promise with the plugin details. resolve({ // Define your plugin's init method, icons, etc. // e.g., // id: 'my_tinymce_plugin', // init: (editor) =&gt; { // editor.ui.registry.addButton('mybutton', { // text: 'My Button', // onAction: () =&gt; editor.insertContent('&amp;nbsp;Hello Moodle!&amp;nbsp;') // }); // }, }); });<i> </i><e>```</e></CODE> <p><STRONG><s>**</s>Step 2.4: Run Grunt to Verify the Fix<e>**</e></STRONG><br/> Open your terminal or command prompt and navigate to your Moodle root directory. Execute the Grunt build command.</p> <CODE lang="bash"><s>```bash</s><i> </i>cd /path/to/your/moodle/root grunt<i> </i><e>```</e></CODE> <LIST><LI><s>* </s><STRONG><s>**</s>Expected Outcome:<e>**</e></STRONG> Grunt should now run without reporting the <C><s>`</s>no-async-promise-executor<e>`</e></C> error. The JavaScript files for your plugin should be successfully linted and processed. If other ESLint errors appear, address them individually.</LI></LIST> <p><STRONG><s>**</s>Step 2.5: Purge Moodle Caches and Test<e>**</e></STRONG><br/> After a successful Grunt build, it's crucial to purge Moodle's caches to ensure the new JavaScript files are loaded.</p> <CODE lang="bash"><s>```bash</s><i> </i>php admin/cli/purge_caches.php<i> </i><e>```</e></CODE> <p>Alternatively, you can purge caches via the Moodle Site Administration UI (<C><s>`</s>Site administration &gt; Development &gt; Purge all caches<e>`</e></C>).</p> <p>Finally, navigate to a Moodle course or activity where the TinyMCE editor is used and test your plugin's functionality to ensure it loads and operates as expected.</p> <p><STRONG><s>**</s>3. Explanation and Best Practices:<e>**</e></STRONG></p> <LIST><LI><s>* </s><STRONG><s>**</s>Why the Override is Acceptable Here:<e>**</e></STRONG> While <C><s>`</s>no-async-promise-executor<e>`</e></C> is a valid ESLint rule for general JavaScript development, Moodle's module loading often necessitates a top-level asynchronous initialization pattern for plugins. Wrapping this in <C><s>`</s>new Promise(async (resolve) =&gt; { ... })<e>`</e></C> allows the plugin to perform <C><s>`</s>await<e>`</e></C> operations (e.g., loading other AMD modules or fetching data) before it signals that it's fully ready. In this specific Moodle context, the <C><s>`</s>async<e>`</e></C> keyword is intentionally used and functionally necessary if <C><s>`</s>await<e>`</e></C> is present, making the ESLint rule overly aggressive. Disabling it for that specific line is a pragmatic solution that maintains code functionality while acknowledging Moodle's architectural patterns.</LI> <LI><s>* </s><STRONG><s>**</s>Alternative (If No <C><s>`</s>await<e>`</e></C> is Truly Needed):<e>**</e></STRONG> If your TinyMCE plugin's initialization is <EM><s>*</s>strictly synchronous<e>*</e></EM> and does not involve any <C><s>`</s>await<e>`</e></C> operations at all, then the <C><s>`</s>async<e>`</e></C> keyword for the executor is indeed redundant. In such a rare case, the <EM><s>*</s>preferred<e>*</e></EM> solution would be to simply remove <C><s>`</s>async<e>`</e></C>: <CODE lang="javascript"><s> ```javascript</s><i> </i> export default new Promise((resolve) =&gt; { // Removed 'async' // Synchronous plugin initialization resolve({ /* plugin details */ }); });<i> </i><e> ```</e></CODE> However, for most Moodle plugins, some form of asynchronous setup is typically required, making the <C><s>`</s>async<e>`</e></C> executor and the ESLint override the more common and appropriate solution.</LI></LIST> <HR>---</HR> <p><STRONG><s>**</s>Recommendation for MoodleDev Documentation Update:<e>**</e></STRONG></p> <p>Yes, the MoodleDev documentation on TinyMCE plugins (<URL url="https://moodledev.io/docs/5.1/apis/plugintypes/tiny">https://moodledev.io/docs/5.1/apis/plugintypes/tiny</URL>) should absolutely be updated to address this common ESLint warning.</p> <p><STRONG><s>**</s>Suggested Update:<e>**</e></STRONG><br/> Add a note or a dedicated troubleshooting section after the <C><s>`</s>plugin.js<e>`</e></C> example, explaining:</p> <LIST type="decimal"><LI><s>1. </s>The <C><s>`</s>no-async-promise-executor<e>`</e></C> ESLint error and why it occurs.</LI> <LI><s>2. </s>The rationale for using <C><s>`</s>async<e>`</e></C> in the Promise executor for Moodle plugins (i.e., enabling <C><s>`</s>await<e>`</e></C> for asynchronous initialization tasks).</LI> <LI><s>3. </s>The recommended solution: <C><s>`</s>// eslint-disable-next-line no-async-promise-executor<e>`</e></C> with an example of its placement.</LI> <LI><s>4. </s>A brief explanation of why this override is considered acceptable in this specific Moodle development scenario.</LI></LIST> <p>This proactive addition to the documentation would significantly improve the developer experience by anticipating and guiding developers through a frequent hurdle.</p></r>

Did this solution help you resolve the issue?