JS :: Simpler interactions between 2 or more script steps

A very good and flexible feature of the Drafts model is that:

  1. A single action can contain multiple script steps, and
  2. a later step can use values (including functions) created by an earlier one.

As the Drafts JS API documentation puts it:

Multiple steps in the same action will execute in the same context, so global variables and functions defined in one step will be available to other steps in the same action.

Things, can of course, get accidentally muddled, (when we have taken two steps from different sources, or written them at different times, for example) if the same variable or constant name happens to be used for different purposes, or in ways that are intended to be separate, in each step.


For example, this would trigger an error, which might seem puzzling:

Script step 1

const a = 1789;

Script step 2

const a = 1789;

Resulting error:

SyntaxError: Cannot declare a const variable twice: 'a'.


A simple solution, sometimes called the Javascript module pattern is to make a habit of wrapping your script-step code in bracketing like this:

(() => {

     // My Drafts 5 step code goes here

})()

or, equivalently,

(function () {

      // My Drafts 5 step code goes here

})()

In other words always write your code, by habit and default, in an β€˜anonymous’ function (function names are optional, and we don’t need one here), which is β€˜immediately invoked’. The trailing parentheses after the function body mean that the function is called as soon as it is defined.

Why does this help ?

Because:

  1. Code inside this bracketing can read and use any global variables defined anywhere outside the bracketing, including in previous script steps, but
  2. No other code is affected by (or can see) anything which we do inside this bracketing.

The result:

  • No unexpected clashes between script steps, and
  • anything we do want to share between steps is just placed outside the module brackets.

No unexpected clashes:

This now works fine – no error message:

Script step 1

(() => {

    const a = 1789;

})()

Script step 2

(() => {

    const a = 1789;

})()

Share just the things we want to share:

If we do want to share anything across steps, we just place it outside the module bracketing, making it global.

Script step 1

const a = 1789;
(() => {

      const x = a;   // 1789
})()

Script step 2
(No need to redefine the value a), and we can use a fresh x without a clash;

(() => {

      const x = a;   // 1789
})()
3 Likes

Thanks for this posting - this was another thing that was puzzling me in your code examples.

1 Like

Another motivation is to avoid polluting the (already cluttered) global name-space.

The Global level of Drafts is less cluttered than a browser JS context, but still contains over 30 name-bindings, amongst which our own variable names must compete and jostle in the absence of a fresh local name space provided by the

(() => {  

    // my code ...

 })()

module bracketing.

(This even FWIW, has a measurable impact on performance, because (if they lack a module-wrapping and the local name space which it creates) our scripts have to search through a bigger and more cluttered space to find the definitions of any variable that we have defined ourselves – see test code below the following list),

Pre-existing variable names in the global context of Drafts 5:

$
Action
Alarm
Calendar
CallbackURL
Credential
Draft
Dropbox
Event
FileManager
GoogleDrive
HTTP
HTTPResponse
Mail
Message
MultiMarkdown
OneDrive
Prompt
Reminder
ReminderList
TJSChecklistItem
TJSContainer
TJSHeading
TJSProject
TJSTodo
action
alert
app
context
device
draft
editor
output
print

Module brackets measurably improve performance

Javascript Core is so fast that performance tends to be a slightly frivolous and irrelevant concern, but it may still be interesting to see that the code snippet below runs significantly faster inside a module wrapper than in the global namespace.

(On this iPad it runs in quarter of a second if module-wrapped, and takes over half a second if run in the unbracketed global name-space)

Slower global name-space version:

// All in the global name-space - slower
	const dteStart = new Date().getTime();
	var
		someValue = 1789,
		i = 10000000,	
		v = undefined;
		
	while (i--) {
		v = someValue;
	}
	const dteEnd = new Date().getTime();
	
	alert(
		JSON.stringify({
			milliseconds: (dteEnd - dteStart), 
			result: v
		}, null, 2)
	);

Faster module-wrapped version:

// All in a local name-space - faster

(() => {

	const dteStart = new Date().getTime();
	var
		someValue = 1789,
		i = 10000000,	
		v = undefined;
		
	while (i--) {
		v = someValue;
	}
	const dteEnd = new Date().getTime();
	
	alert(
		JSON.stringify({
			milliseconds: (dteEnd - dteStart), 
			result: v
		}, null, 2)
	);
})();

4 Likes

This helped me today. Thanks for writing this up!

1 Like

Thanks for the tip.
Makes the code uglier though.

Maybe I would consider using a named local context (like https://stackoverflow.com/questions/881515/how-do-i-declare-a-namespace-in-javascript#5947280)

(function( skillet, $, undefined ) {
      //public function
      skilled.interface = function() {
           //...
       }

      //private 
      function local_function() {
           //..
       }

But how to register this in drafts?
What do you think?