ES6+ Features: Promises
Relevant source files
The following files were used as context for generating this wiki page:
The Jurassic engine provides an implementation of ECMAScript 6 Promises for managing asynchronous operations. This implementation includes the core functionality for creating and chaining promises, as well as static methods for handling groups of promises. According to the project's documentation, support for Promises is a work in progress, with 4 out of 8 features from the ES6 specification implemented.
A standout feature of Jurassic's implementation is its deep integration with the .NET Task Parallel Library (TPL). This allows for seamless conversion between JavaScript Promise objects and .NET System.Threading.Tasks.Task objects, providing a powerful bridge between asynchronous code in the two environments.
Sources: README.md, Test Suite Runner/Kangax/compat-table.json
Core Concepts and Lifecycle
A PromiseInstance in Jurassic represents the eventual completion (or failure) of an asynchronous operation and its resulting value. Each promise exists in one of three states, managed internally by the PromiseState enum.
Sources: Jurassic/Library/Promise/PromiseInstance.cs:19-24
Promise States
| State | Description |
|---|---|
Pending | The initial state; the promise is neither fulfilled nor rejected. |
Fulfilled | The operation completed successfully, and the promise has a resulting value. |
Rejected | The operation failed, and the promise has a reason for the failure. |
Sources: Jurassic/Library/Promise/PromiseInstance.cs:21-23
Promise Lifecycle Diagram
The following diagram illustrates the lifecycle of a PromiseInstance. A promise starts in the Pending state and can only transition once to either Fulfilled (on resolve) or Rejected (on reject). Once settled, a promise's state is immutable.
Creating a Promise
A new promise is created using the Promise constructor, which takes a single argument: an executor function. This function is executed immediately and is passed two function arguments: resolve and reject.
var p = new Promise(function(resolve, reject) {
// Asynchronous operation here.
// If successful, call resolve(value).
// If it fails, call reject(reason).
});
Calling the constructor without an executor function results in a TypeError.
Sources: Jurassic/Library/Promise/PromiseConstructor.cs:49-58, Unit Tests/Library/PromiseTests.cs:33-35
PromiseConstructor
The PromiseConstructor class implements the static properties and methods of the global Promise object.
Sources: Jurassic/Library/Promise/PromiseConstructor.cs, Jurassic/Library/Promise/PromiseConstructor.g.cs
Static Methods
| Method | Description |
|---|---|
Promise.resolve(value) | Returns a PromiseInstance that is resolved with the given value. If the value is a "thenable" (i.e., has a .then method), the returned promise will "follow" that thenable. |
Promise.reject(reason) | Returns a PromiseInstance that is rejected with the given reason. |
Promise.all(iterable) | Returns a single promise that fulfills when all of the promises in the iterable argument have fulfilled, or rejects as soon as one of the promises in the iterable rejects. |
Promise.race(iterable) | Returns a promise that fulfills or rejects as soon as one of the promises in the iterable fulfills or rejects, with the value or reason from that promise. |
Sources: Jurassic/Library/Promise/PromiseConstructor.g.cs:14-20, Unit Tests/Library/PromiseTests.cs
Promise.all() Flow
The diagram below shows the logic for Promise.all(). It waits for all input promises to resolve and aggregates their results into an array. If any input promise rejects, the main promise immediately rejects with that reason.
PromiseInstance
The PromiseInstance class represents a single promise and provides methods for chaining asynchronous actions.
Sources: Jurassic/Library/Promise/PromiseInstance.cs, Jurassic/Library/Promise/PromiseInstance.g.cs
Instance Methods
| Method | Description |
|---|---|
promise.then(onFulfilled, onRejected) | Appends fulfillment and rejection handlers to a promise. It returns a new promise, allowing for method chaining. |
promise.catch(onRejected) | Appends a rejection handler callback to the promise, and returns a new promise. It is a shorthand for promise.then(null, onRejected). |
promise.finally(onFinally) | Schedules a function to be called when the promise is settled (either fulfilled or rejected). It returns a new promise that resolves with the original promise's value. |
Sources: Jurassic/Library/Promise/PromiseInstance.g.cs:13-19
Asynchronous Execution with .then()
Callbacks attached with .then() are always executed asynchronously, after the current script block has completed, even if the promise is already settled. This is managed by the script engine's event queue.
The following sequence diagram shows this asynchronous flow.
A test case confirms this behavior, where a variable is checked immediately after the .then() call and is only updated after the script context yields.
// File: Unit Tests/Library/PromiseTests.cs:141-143
Assert.AreEqual(1, Evaluate("var f = 1; Promise.resolve().then(function() { f = 2; }); f"));
Assert.AreEqual(2, Evaluate("f"));
Sources: Unit Tests/Library/PromiseTests.cs:141-143
.NET Interoperability
Jurassic provides methods to convert between PromiseInstance and .NET System.Threading.Tasks.Task, facilitating integration between JavaScript and C# asynchronous code.
Sources: Unit Tests/Library/PromiseTests.cs:660-785
PromiseInstance.CreateTask()
This method converts a PromiseInstance into a Task<object>.
- If the promise is fulfilled, the task completes with the promise's result.
- If the promise is rejected, the task faults with a
JavaScriptException.
// File: Unit Tests/Library/PromiseTests.cs:665-666
var promise = EvaluatePromise("Promise.resolve(1);");
Assert.AreEqual(1, (int)await promise.CreateTask());
Sources: Unit Tests/Library/PromiseTests.cs:660-675
PromiseConstructor.FromTask(Task)
This static method converts a .NET Task or Task<T> into a PromiseInstance.
- If the task completes successfully, the promise is fulfilled with the task's result (or
undefinedfor a non-genericTask). - If the task faults, the promise is rejected. A
JavaScriptExceptionis unwrapped, while other exceptions result in a rejection where the reason is the exception's message.
// File: Unit Tests/Library/PromiseTests.cs:721-726
var tcs = new TaskCompletionSource<object>();
var promise = ScriptEngine.Promise.FromTask(tcs.Task);
ScriptEngine.SetGlobalValue("promise", promise);
Execute("promise.then(function (r) { throw r }, function (r) { testingContext.push(r) })");
tcs.SetException(new InvalidOperationException("This is an invalid operation exception."));
When the event queue is processed, testingContext will contain the exception message.
Sources: Unit Tests/Library/PromiseTests.cs:721-736
Conclusion
The Jurassic engine's Promise implementation provides the fundamental building blocks for modern asynchronous JavaScript. While not all ES6 features like subclassing are supported, the existing functionality is robust and well-tested. The most significant aspect is the seamless two-way integration with the .NET Task Parallel Library, which makes Jurassic a powerful tool for hybrid .NET/JavaScript applications that require complex asynchronous logic.
Sources: README.md, Test Suite Runner/Kangax/compat-table.json