$ npm install nodent
NoDent is a small module for Nodejs that implements the JavaScript ES7 keywords async
and await
. These make writing, reading and understanding asynchronous and callback methods more implicit and embedded in the language. It works by (optionally) transforming JavaScript when it is loaded into Node.
This README assumes you're using Nodent v3.x.x - see Upgrading if your upgrading from an earlier version, and keep an eye out for the asides that look like..
v2.x users - changes between NoDent v2 and v3 are highlighted like this
You can now see what Nodent does to your JS code with an online demo at here. Within the examples in this README, click on TRY-IT to see the code live.
Declare an asynchronous function (one that returns "later").
async function tellYouLater(sayWhat) {
// Do something asynchronous and terminal, such as DB access, web access, etc.
return result ;
}
Call an async function:
result = await tellYouLater("Hi there") ;
To use NoDent, you need to:
require('nodent')() ;
This must take place early in your app, and need only happen once per app - there is no need to require('nodent')
in more than one file, once it is loaded it will process any files ending in use nodent
directive at the top of a .js file.
v2.x users, the '.njs' extension is still supported, but not recommended
You can't use the directive, or any other Nodent features in the file that initially require("nodent")()
. If necessary, have a simple "loader.js" that requires Nodent and then requires your first Nodented file, or start your app with nodent from the command line:
./nodent.js myapp.js
That's the basics.
npm install --save nodent
You can invoke and run a nodented JavaScript file from the command line (although for Node apps it's much easier using the JS transpiler). To load, compile and run your JS file (containing async
and await
), use:
./nodent.js myNodentedFile.js
You can also simply compile and display the output, without running it. This is useful if you want to pre-compile your scripts:
./nodent.js --out myNodentedFile.js
If you are using nodent as part of a toolchain with another compiler, you can output the ES5 or ES6 AST is ESTree format:
./nodent.js --ast myNodentedFile.js
...or read an AST from another tool
./nodent.js --fromast --out estree.json // Read the JSON file as an ESTree AST, and output the nodented JS code
To generate a source-map in the output, use --sourcemap
.
./nodent.js --sourcemap --out myNodentedFile.js
The testing options --parseast
and --minast
output the source as parsed into the AST, before transformation and the minimal AST (without position information) respectively. The option --pretty
outputs the source formatted by nodent before any syntax transformation. You can read the Javascript or JSON from stdin (i.e. piped) by omitting or replacing the filename with -
.
The full list of options is:
option | Description |
---|---|
--fromast | Input is a JSON representation of an ESTree |
--parseast | Parse the input and output the ES7 specification ESTree as JSON |
--pretty | Parse the input and output the JS un-transformed |
--out | Parse the input and output the transformed ES5/6 JS |
--ast | Parse the input and output the transformed ES5/6 ESTree as JSON |
--minast | Same as --ast, but omit all the source-mapping and location information from the tree |
--exec | Execute the transformed code |
--sourcemap | Produce a source-map in the transformed code |
--runtime | Include the nodent runtime in the output |
Code generation options:
option | Description |
---|---|
--use=mode | Ignore any "use nodent" directive in the source file, and force compilation mode to be es7 ,promises ,generators . engine or default |
--wrapAwait | Allow await with a non-Promise expression more info... |
--lazyThenables | Evaluate async bodies lazily in 'es7' mode. See the Changelog for 2.4.0 for more information |
--noruntime | Compile code (in -promises or -engine mode) for execution with no runtime requirement at all |
--es6target | Compile code assuming an ES6 target (as of v3.0.8, this only requires support for arrow functions) |
--noextensions | Don't allow nodent's extensions to the ES7 specification more info... |
There is no need to use the command line at all if you want to do is use async
and await
in your own scripts then just require('nodent')()
. Files are transformed if they have a use nodent
directive at the top, or have the extension ".njs". Existing files ending in '.js' without a use nodent...
directive are untouched and are loaded and executed unchanged.
Nodent can generate code that implements async
and await
using basic ES5 JavaScript, Promises (via Nodent's built-in library, a third party library or module, or an ES5+/6 platform) or Generators (ES6). Using the directive:
'use nodent';
The use nodent
directive uses a default set of compilation options called 'default', which can be modifed in your package.json.
Within your package.json, you can have named sets of pre-defined options, which individual files can refer to if necessary. There are four pre-defined sets of options: promises, es7, generators and engine.
'use nodent-promises';
'use nodent-es7';
'use nodent-generators';
'use nodent-engine';
All the implementations work with each other - you can mix and match. If you're unsure as to which will suit your application best, or want to try them all out 'use nodent';
will use a 'default' configuration you can determine in your application's package.json. See Advanced Configuration for details.
use nodent-es7
- it's the most compatible as it doesn't require any platform support such as Promises or Generators, and works on a wide range of desktop and mobile browsers.
use nodent-promises
provides the most compatibility between modules and apps. If your module or library targets Node earlier than v4.1.x, you should install a Promise library (e.g. rsvp, when, bluebird) or use nodent.Thenable
to expose the Promise API. In promises mode, there is no need for a runtime at all. Specifying the option use nodent-promises {"noRuntime":true}
will generate pure ES5 code at the cost of some loss in performance and increase in code size.
v2.x users:
nodent.EagerThenable()
is still defined, but as of v3 is the same as theThenable
implementation.
use nodent-generators
generates code which is reasonably easy to follow, but is best not used for anything beyond experimentation as it requires an advanced browser on the client-side, or Node v4.x.x. The performance and memory overhead of generators is poor - currently (Node v6.6.0) averaging 3.5 times slower compared to the es7 with 'lazyThenables'.
use nodent-engine
does not transpile standard ES7 async/await constructs, but only transpiles the additional non-standard features provided by nodent - await anywhere, async getters, async return and throw. At the time of writing, not many runtimes implement async and await - Chrome v53 does with command line flags, and Edge 14 are examples. On Chrome, performance is better than generators, but not quite as good as Promises, and still less than half the speed of ES7 mode. In promises mode, there is no need for a runtime at all. Specifying the option use nodent-engine {"noRuntime":true}
will generate pure ES5 code at the cost of some loss in performance and increase in code size.
To compile code programmatically you should use require(nodent-compiler
). This module is a standalone compiler that has:
Function.prototype
, a global error handler, stack source-mapping)Example usage:
var NodentCompiler = require('nodent-compiler');
var compiler = new NodentCompiler() ;
var es5ReadySourceCode = compiler.compile(sourceCode, filename, { sourcemap:false, promises: true, noRuntime: true, es6target: true });
You can use async
and await
within a browser by auto-parsing your scripts when Nodejs serves them to your clients.
The exported function generateRequestHandler(path, matchRegex, options)
creates a node/connect/express compatible function for handling requests for nodent-syntax files that are then parsed and served for use within a stanadrd browser environment, complete with a source map for easy debugging.
For example, with connect:
var nodent = require('nodent')() ;
...
var app = connect() ;
...
app.use(nodent.generateRequestHandler(
"./static-files/web", // Path to where the files are located
/\.njs$/, // Only parse & compiles ending in ".njs"
options // Options (see below)
)) ;
The regex can be omitted, in which case it has the value above.
The currently supported options are:
enableCache: <boolean> // Caches the compiled output in memory for speedy serving.
runtime: <boolean> // Set to precede the compiled code with the runtime support required by Nodent
extensions: <string-array> // A set of file extensions to append if the specified URL path does not exist.
htmlScriptRegex: <optional regex> // If present, Nodent will attempt to read and parse <script> tags within HTML files matching the specified regex
compiler:{ // Options for the code generator
es7:<boolean>, // Compile in es7 mode (like 'use nodent-es7')
promises:<boolean>, // Compile in Promises mode (like 'use nodent-promises')
generators:<boolean>, // Compile in generator mode (like 'use nodent-generators')
engine:<boolean>, // Compile in engine mode (like 'use nodent-engine')
sourcemap:<boolean>, // Create a sourcemap for the browser's debugger
wrapAwait:<boolean>, // Allow 'await' on non-Promise expressions
lazyThenables:<boolean>, // Evaluate async bodies lazily in 'es7' mode. See the Changelog for 2.4.0 for more information
noRuntime:<boolean>, // Only compatible with promises & engine. Generate pure ES5 code for an environment that support Promises natively or as a global declaration. Currently about 15% slower that using the built-in runtime $asyncbind. Default is false.
es6target:<boolean> // Compile code assuming an ES6 target (as of v3.0.8, this only requires support for arrow functions)
}
setHeaders: function(response) {} // Called prior to outputting compiled code to allow for headers (e.g. cache settings) to be sent
Note that parsing of script tags within HTML is relatively simple - the parsing is based on regex and is therefore easily confused by JS strings that contain the text 'script', or malformed/nested tags. Ensure you are parsing accurate HTML to avoid these errors. Scripts inline in HTML do not support source-mapping at present.
If you're using a modern browser with Promise support (or including a third-party promise library), you can specify the compiler filag noRuntime: true
, which will generate pure ES5 code at the cost of some loss in performance and increase in code size. Otherwise, you'll need to provide some support routines at runtime (i.e. in the browser):
Function.prototype.$asyncbind
Function.prototype.$asyncspawn
if you're using generators
Object.$makeThenable
if you're using the wrapAwait
option and not using promises (see await with a non-Promise
window.$error
if you use await outside of an async function, to catch unhandled errors, for example:
// Called when an async function throws an exception during // asynchronous operations and the calling synchronous function has returned. window.$error = function(exception) { // Maybe log the error somewhere throw ex ; };
This are generated automatically in the transpiled files when you set the runtime
option, and declared when Nodent is loaded (so they are already avaiable for use within Node).
Further information on using Nodent in the browser can be found at https://github.com/MatAtBread/nodent/issues/2.
You can also invoke nodent from browserify as a plugin, or as an alternative, faster implementation than than Babel's transform-async-to-generator
You can find out more about defining and calling async functions here. There's plenty on the web too.
Async programming with Nodent (or ES7) is much easier and simpler to debug than doing it by hand, or even using run-time constructs such as Promises, which have a complex implementation of the their own when compiled to ES5. However, a couple of common cases are important to avoid.
You can continue to use all the Nodent extensions with async/await capable engines. In the use nodent-engine
mode, all ES7 standard async/await constructs are passed through unchanged, and only functions that use a Nodent extension are transformed.
You can disable the extensions (but not the known differences) by compiling with flag parser:{ noNodentExtensions: true}
or the command line option --noextensions
. Nodent will pass the code unalterted to the parser (acorn) which will fail the syntactic extensions. In this scenario, the dependency acorn-es7-plugin
is not required (but will still be installed by default). The option only works with acorn >4.x.
Extensions to the specification:
Nodent permits a class or object definition to define async getters:
async get data() { ... }
get async data() { ... }
class MyClass {
static async name() { ... }
}
The ES7 async-await spec states that you can only use await inside an async function. This generates a warning in nodent, but is permitted. The synchronous return value from the function is compilation mode dependent. In practice this means that the standard, synchronous function containing the await
does not have a useful return value of it's own.
The statements async return <expression>
and async throw <expression>
are proposed extensions to the ES7 standard (see https://github.com/lukehoban/ecmascript-asyncawait/issues/38). The alternative to this syntax is to use a standard ES5 declaration returning a Promise. See below for details.
Known differences from the specification:
The AsyncFunction
type is not defined by default, but is returned via the expression require('nodent')(...).require('asyncfunction')
. The AsyncFunction
constructor allows you to create async functions on the fly, just as the standard Function
constructor does.
As of the current version, case
blocks without a break;
that fall thorugh into the following case
do not transform correctly if they contain an await
expression. Re-work each case
to have it's own execution block ending in break
, return
or throw
. Nodent logs a warning when it detects this situation.
The ES7 specification allows an application to await
on a non-Promise value (this occurs because the template implementation wraps every generated value in a Promise). So the statement:
var x = await 100 ; // 100
...is valid. Nodent, by default, does not allow this behaviour (you'll get a run-time error about '100.then is not a function'. Generally, this is not a problem in that you obviously only want to wait on asynchronous things (and not numbers, strings or anything else). However, there is one unpleasant edge case, which is where an expression might be a Promise (my advice is to never write code like this, and avoid code that does).
var x = await maybeThisIsAPromise() ;
In this case, the expression will need wrapping before it is awaited on by Nodent. You can emulate this behaviour by specifying the code-generation flag 'wrapAwait' in your package.json or after the nodent directive:
'use nodent {"wrapAwait":true}';
Wrapping every value in a Promise increases the time taken to invoke an async function by about 20%. An alternative to wrapping everything is to only wrap expression where this might be the case explicitly:
var x = await Promise.resolve(maybeThisIsAPromise()) ;
or
var isThenable = require('nodent').isThenable ;
...
var x = maybeThisIsAPromise() ;
if (isThenable(x))
x = await x ;
The second implementation avoids the expense (20%) of wrapping every return value in a Promise, with the extra code for testing if it is a Promise before awaiting on it.
v2.x users - lazyThenables are only available in -es7 mode in v3, and the nodent.Thenable implementation is not lazy, as it was in v2.x.
Invoking an async function without a preceding await
(simply by calling it) executes the function body but you can't get the result. This is useful for initiating 'background' things, or running async functions for their side effects. This is in compliance with the ES7 specification.
However, this has a performance overhead. For maximum performance, you can specify this code generation option in use nodent-es7 {"lazyThenables":true}
mode. In this mode, if you call the async function the body is not actually executed until resolved with an await
(or a .then()
). If you know your code always uses await
, you can use this option to improve performance.
In use nodent-promises
mode, it is the implementation of the Promise that determines the execution scheduling and performance. The table below is a summary of modes and execution semantics. You can test the performance on your own hardware with the following command. Note the relative performance is a worst case, since the test does nothing other than make async calls in a loop.
./nodent.js tests tests/semantics/perf.js
Mode | Flags / Implementation | Lazy / Eager | Possibly sync resolution | Performance (relative) |
---|---|---|---|---|
es7 | lazyThenable | Lazy | Yes | 1.0 |
es7 | (none) | Eager | No | 1.7x slower |
promises | nodent | Eager | No | 1.7x slower |
promises | node 6.6 native | Eager | No | 5.2x slower |
promises | bluebird 3.4.6 | Eager | No | 2.0x slower |
promises | rsvp 3.3.1 | Eager | No | 2.2x slower |
promises | when 3.7.7 | Eager | No | 1.6x slower |
generators | nodent | Eager | No | 7.5x slower |
generators | node 6.6 native | Eager | No | 15.0x slower |
generators | bluebird 3.4.6 | Eager | No | 8.5x slower |
generators | rsvp 3.3.1 | Eager | No | 7.6x slower |
generators | when 3.7.7 | Eager | No | 8.3x slower |
All other JavaScript ES5/6/2015 constructs will be transformed as necessary to implement async
and await
.
v2.x users - note the timings and execution semantics for Thenable (and EagerThenable) have changed: they are now fully Promise/A+ compliant, meaning they resolve asynchronously and evaluate eagerly. Only -es7 lazyThenable mode might resolve synchronously.
Specifically in Nodent (not specified by ES7), you can interface an ES7 async function with a old style callback-based function. For example, to create an async function that sleeps for a bit, you can use the standard setTimeout function, and in its callback use the form async return <expression>
to not only return from the callback, but also the surrounding async function:
async function sleep(t) {
setTimeout(function(){
// NB: "async return" and "async throw" are NOT ES7 standard syntax
async return undefined;
},t) ;
}
Similarly, async throw <expression>
causes the inner callback to make the container async function throw an exception. The async return
and async throw
statements are NOT ES7 standards (see https://github.com/tc39/ecmascript-asyncawait/issues/38). If you want your code to remain compatible with standard ES7 implementations when the arrive, use the second form above, which is what nodent would generate and is therefore ES5/6/7 compatible.
Nodent has two sets of configuration values:
The first is defined once per installation (Nodent contained as dependencies within dependencies have their own, per-installation, instances). You can 'redefine' the values, but the effect is to overwrite existing settings. These are specified as the first argument when you require('nodent')(options)
. Details of the options are below.
The second set is defined per-file for each file that Nodent loads and compiles. The options are:
Member | Type | |
---|---|---|
es7 | boolean | set by any use nodent... directive |
promises | boolean | set by the directive use nodent-promises and use nodent-generators |
generators | boolean | set by the directive use nodent-generators |
engine | boolean | set by the directive use nodent-engine |
wrapAwait | boolean | default: false - allow await followed by a non-Promise more info... |
sourcemap | boolean | default: true - generate a source-map in the output JS |
noRuntime | boolean | default: false - generate pure ES5 code with external dependencies. The code is bigger and slower, and only works with -promises or -engine |
es6target | boolean | default: false - use ES6 constructs to improve code speed and size (as of v3.0.8, this only requires support for arrow functions) |
parser | object | default: {sourceType:'script'} - passed to Acorn to control the parser |
mapStartLine | int | default: 0 - initial line number for the source-map |
generatedSymbolPrefix | string | default '$': string used to disambiguate indentifiers created by the compiler |
The members $return, $error, $arguments, $asyncspawn, $asyncbind, $makeThenable represent the symbols generated by the compiler. You could change them to avoid name clashes, but this is not recommended.
When determining what options to use when compiling an individual file, nodent follows the sequence:
Use the set specified after the use nodent-
directive. For example use nodent-promises
uses a predefined set called 'promises'. Other predefined sets are 'es7', 'generators' and 'engine'. If the use nodent
doesn't have a name, the internal name "default" is used.
Apply any modifications contained within the package.json containing your module or application. The package.json can (optionally) contain a 'nodent' section to define your own sets of options. For example, to create a set to be used by files containing a use nodent-myapp
directive:
"nodent":{
"directive":{
"myapp":{
"promises":true,
"wrapAwait":true
}
}
}
You can also change options for the pre-defined sets here (default, es7, promises, generators, engine).
v2.x users - Until v2.5.4, Nodent would typically look for your application's package.json. All later versions use the installation location of the calling code, so an application, module and sub-module can all have their own settings and defaults.
Finally, nodent applies any options specified within the directive, but after the name. The options are strict JSON and cannot be an expression. This is useful for quickly testing options, but is probably a bad idea if applied to very many files. One exception is rare use of the wrapAwait
option, which has a performance overhead and few genuine use-cases. For example, to create the same effect as the 'myapp' set above:
'use nodent-promises {"wrapAwait":true}';
You can programmatically set these options before creating the nodent compiler (but after requiring nodent) by using the setDefaultCompileOptions() and setCompileOptions() API calls, however it is more flexible and less likely to clash with another module if you use the techniques above.
Within a nodented file, the special symbol __nodent
is expanded out to the current option set. It is not a variable and cannot be assigned to - it is an object literal. This has few useful use-cases, except for testing. An example is here
Create an instance of a nodent compiler:
var nodent = require('nodent')(options);
Options:
Member | Type | |
---|---|---|
dontMapStackTraces | boolean | default: false |
augmentObject | boolean | Adds asyncify(PromiseProvider) and isThenable() to Object.prototype, making expressions such as var client = new DB().asyncify(Promise) and if (abc.isThenable()) await abc() less verbose |
extension | string | extension for files to be compiled (default: '.njs'). Note that this is unused if the file has a use nodent- directive. |
log(msg) | function | Called when nodent has a warning or similar to show. By default they are passed to console.warn(). Set this member to change how to record logging, or to false to disable logging. |
v2.x The flag 'asyncStackTrace' has been removed as modern debuggers can do this better than nodent can. You can specify it, but it is ignored.
Return: a 'nodent' compiler object with the following properties:
Member | Type | |
---|---|---|
version | string | The currently installed version |
asyncify (PromiseProvider) | function | Return a function to convert an object with functions with callbacks to ones with async function members. asyncify is also a meta-property (see below) |
Thenable(function) EagerThenable()(function) | function | Nodent's in-built Promise implementation. Thenable is also a meta-property (see below) |
require(moduleName,options) | object | Import an async helper module |
generateRequestHandler(path, matchRegex, options) | function | Create a function use with Express or Connect that compiles files for a browser on demand - like a magic version of the 'static' middleware |
isThenable (object) | boolean | Return boolean if the supplied argument is Thenable (i.e. has an executable then member). All Promises, nodent.EagerThenable() and nodent.Thenable return true |
$asyncbind | function | Runtime required in -promises and -engine mode if not compiled with noRuntime:true |
$asyncspawn | function | Runtime required in generator mode |
Note the nodent object has other members used for implementation - these are subject to change and are not part of the API.
v2.x users - nodent.Thenable and nodent.EagerThenable() are now full Promises/A+-compliant Promise implementations. There is no external access to the synchronous 'Thenable' used in -es7-lazyThenables mode.
You can over-ride certain defaults and access values that are global to the process (as opposed to module by module) by instantiating nodent without an argument:
var nodentMeta = require('nodent') ;
The available meta-properties are:
Member | Type | |
---|---|---|
Thenable | function | Nodent's built in Promise/A+ implementation |
asyncify | object | Method to transform methods from callbacks to async functions by wrapping in Promises |
setDefaultCompileOptions (compiler[,env]) | function | Set the defaults for the compiler and environment. This should be called before the first compiler is created. The default environment options (log augmentObject extension dontMapStackTraces asyncStackTrace ) will be used when the corresponding option is missing when the compiler is created. The compiler options (sourcemap and default symbol names) must be set before the first compiler is created. The other compilation options (es7 promises generators engine ) are set by the corresponding directive |
setCompileOptions (name,compiler) | function | Set the compilation options for a named directive for the compiler. This should be called before the first compiler is created. |
// Turn off sourcemap generation:
nodentMeta.setDefaultCompileOptions({sourcemap:false},{asyncStackTrace:true})
// Access values that are global to all nodent compiler instances
var Promise = global.Promise || nodentMeta.Thenable ; // Set a Promise provider for this module
nodentMeta.asyncify
You still need to (at least once) create the compiler:
var nodent = nodentMeta(options) ;
The return ('compiler') has the additional, instance specific properties specified in the API above.
Nodent has a small set of covers for common Node modules. More information can be found here
Nodent has a test suite (in ./tests) which is itself a node package. Since it requires a bunch of Promise implementations to test against, it is NOT installed when 'nodent' is installed. The Promise implementations are option - you can run the tests using the nodent.Thenable and native Promises (if available) without installing any other modules. To run the tests:
npm test
If you want to run the tests against some popular Promise libraries:
cd tests
npm install
cd ..
./nodent.js tests
The test runner in tests/index.js accepts the following options:
./nodent.js tests [OPTIONS] [test-files]
--quick Don't target a specific execute time, just run each test once
--nogenerators Performance test syntax transformations only, not generators
--genonly Only run the performance tests for generator mode
--noengine Performance test the underlying engine's support for async and await (e.g. Chrome v53, node.js v7 with flags)
--syntax Check the parser/output code before running semantic tests
--syntaxonly Only run syntax tests
--notStrict Run the tests without a 'use strict' inserted at the top of every test file (NB: the reverse --forceStrict was the default until v3.0.11)
--noNodentExtensions Compile the tests disallowing nodent's extensions to ES7 specification. This will generate test failures
v2.x users - The flag --generators has been replaced by --nogenerators, which has the opposite sense.
Run the test script without the --quick
option to see how nodent code performs in ES7 mode, Promises, generators and engine on your platform. The specific 'perf' test just calls and awaits in a tight loop:
./nodent.js tests tests/semantics/perf.js
Additionally, a try the following links to test performance against Babel and Traceur.
nodent 356ms
babel 1072ms - more than 3x slower
traceur 1175ms - more than 3x slower
The example timings are from Chrome v53 on Mac OSX. I get even wider results with Firefox, and dramatically wider results on mobiles (nodent ES7 mode is upto 10x faster than generators and transpilers).
The test is a simple set of nested loops calling async functions that don't do much. The purpose is to illustrate the overhead generated in the transpilation by each compiler. In reality, you'd be crazy to use async calls for everything, but very well advised to use them for I/O bound operations (network, disks, streams, etc). In these cases, you can be reasonably certain that the overhead generated by the compilers would be small in comparison to the actual operation....but it's nice to know you're not wasting cycles, right? For those who want to know why, the real reason is the use of generators (the suggested implementation in the ES7 async/await specification), which are inefficient natively (about 50% slower than using 'nodent-promises'), and even worse when transcompiled into ES5.
v3.0.0
Nodent v3 is a significant update. If your project isn't using any internal functions or monkey patching, you should just be able to upgrade by changing your package.json - with very few exceptions the external API and features are the same. Major changes are:
The Promise implementation used by Nodent by default is based on the most excellent Zousan Promise library. After a lot of looking around and testing, Zousan proved to be one of the smallest and fastest around - small enough to fit
into Nodent's runtime and faster in most practical cases than Bluebird and when. The speed is enhanced compared to most libraries as Nodent rarely generates code that relies on Promise chaining or having multiple .then()
calls on the same Promise (v2 never did this, v3 does). Zousan is optimal in these cases as only creates the required data lazily.
Promises are now the default execution mode. Only -es7 with lazyThenables uses the synchronous Thenable protocol. This is only retained for speed in exceptional cases. In almost all practical applications (i.e. using async functions to handle IO of some sort), the overhead is around 20% per call, meaning it is trivial compared to the IO operation. Nodent syntax-transformation remains around 3-4 times faster than both native generators and libraries like regenerator.
The use of Promises/A+ compliant execution means the recursive loop asynchronisation used in Nodent v2 can be unwound. Specifically, for
, while
and do...while
loops in Nodent v2 were recursive if the loop didn't yield control to an async operation. This meant that you could run out of stack on relatively trivial loop. For example:
for (var i=0; i<cache.length; i++) {
if (cache[i].entry === null)
cache[i].entry = await readNetworkResult() ;
}
This loop would appear to run fine while the cache hadn't been emptied, or was no more than a few hundred items long. However, if the test for 'emptiness' was never met, and the cache was thousands of items long, because the loop could be asynchronous, it would recurse deeply and cause the process to exit with a stack overflow.
Nodent v3 doesn't use recursion (it uses a trampoline), but in doing so it requires Promise chaining, which was not supported by nodent Thenables.
Nodent will still generate potentially recursive loops if you specify use nodent-es7 {"lazyThenables":true}
since the basic lazy Thenable (while small and very fast) doesn't support chaining.
This execution case was pointed out by https://github.com/jods4 - many thanks.
The new code-generation option noRuntime
will generate pure-ES5 code if you are using -promises, and pure-ES7 if you are using -engine. Other modes (-es7, -generators) still require a small runtime.
The new code-generation option es6target
allows nodent to use some ES6 features to optimize code size and speed (for example, arrow functions in preference to calling bind(this)
). As of v3.0.0 no differences are implemented, but if features are used they will be documented. This option is intended for targets such as node >=4.0.0 or modern browsers. It is unlikely to be worthwhile specifying an es6target if you intend to transpile that es6 to es5 (e.g. with Babel) - in that case don't specify the option and compile straight to ES5.
The helper function noDentify
which was deprecated in v2 has now been removed from the main Nodent library. It can be required by Function.prototype.noDentify = nodent().require('nodentify')
if needed.
12-Jul-18 v3.2.8 (3.2.7)
asyncify
so that it doesn't try and asyncify getters, even if they return a function. In node v10 this recurses as fs.ReadStream
is a getter that does a lazy load, and for some reason it fails on Ubuntu (maybe all linux).
Also, always push the promised callback, rather than forcing it into the final argument position, as it breaks logic used by fs.stat
to determine its optional arguments. It is possible this change will break
other function members that use unusual methods for determining the number and type of arguments.02-May-18 v3.2.6 (3.2.5)
const
semantics should be treated as pre-ES6 (NodeJS <=5.x) or ES6 in favour of ES6 semantics. Note that this may change the execution path of code which previously made the opposite assumption in NodeJS <5.x and realted JS engines. (More details at https://github.com/MatAtBread/nodent-transform/commit/5fa3d01400ffb4903fe4184c3886f5d8b1c223b0)var
declarations within loop bodies.30-Apr-18 v3.2.4
catch
body was incompletely transformed (see https://github.com/MatAtBread/nodent/issues/109)24-Apr-18 v3.2.2
18-Apr-18 v3.2.0
nodent-compiler @3.2.0
, which in turn uses the nodent-transform@3.2.0
, which is the base AST transformer (with no parser or code generator), which is also used
from Babel 7-beta (see https://github.com/babel/babel/pull/7076) and forms the basis of leaner (external) fast-async
Babel plugin.10-Apr-18 v3.1.3-8
21-Aug-17 v3.1.2
18-Aug-17 v3.1.1
es6target
, noRuntime
, promises
and engine
to be defined with the value "host"
, in which case they are determined from the execution environment the code was compiled in (ie. the version of nodejs). This allows transpiled code to gracefully compile across node >0.10 using additional features of each platform as they are available. An addition named set of options, also called "host"
is defined to use these values, allowing the directive "use nodent-host"
to compile code for the current platform. The new value can also be used to set (or reset) names sets in the package.json. For example, to set "host" as the default values: "nodent":{
"directive":{
"default":{
"es6target":"host",
"promises":"host",
"engine":"host",
"noRuntime":"host"
}
}
}
12-Jul-17 v3.1.0
This version splits the CLI, require hook and options parsing (use nodent
directive, package.json options) from the core compiler.
If your code monkey patches the internals of nodent, it may no longer work, as the core compiler has been moved into a submodule.
nodent-compiler
.nodent-runtime
nodent
.If you are not requiring individual components within nodent
or otherwise dependent on the internal structure, no changes will be required.
04-Jul-17 v3.0.20
29-Mar-17 v3.0.17
28-Mar-17 v3.0.16
07-Mar-17 v3.0.14
arguments
is incorrectly mapped within async functions (Babel trees/fast-async only)arguments
in arrow functions (name is mapped, but no local arguments is created)await
await
leading to var url = '/', r = await get(url)
failing due to url
being not yet assigned.await
try
block has completed incorrectly invokes the catch block it contains, rather than the one it is contained in14-Feb-17 v3.0.12
async return|throw
extension if nested more than one function level deep (see tests/semantics/nested-async-exits.js)engine
in configuration object so it can be programmatically over-riddenuse strict
in tests unless --notStrict
is specifiedstrict
mode nested function hoisting to mimic the semantics of recent browsers19-Jan-17 V3.0.11
12-Jan-17 v3.0.9
20-Dec-16 v3.0.8
es6target
option is specified. This generates async call sequences that run approximately 20% faster (on on V8 v5.4.x) as calls to bind(this)
and $asyncbind(this)
are omitted from the generated code, especially when noRuntime
is also specified.23-Oct-16 v3.0.5, v3.0.4
super
15-Oct-16 v3.0.3
10-Oct-16 v3.0.2
06-Oct-16 v3.0.0 see above
28-Sep-16 v2.6.10
25-Sep-16 v2.6.9
async(()=>0)
Thanks to the people who reported bugs, provided test cases and fixes while I was working on v3. Here's a few that I remember, in alpha order:
© 2010 - cnpmjs.org x YWFE | Home | YWFE