This proposal seeks to add support for Decorators on function expressions, function declarations, and object literal elements.
Stage: 0
Champion: Ron Buckton (@rbuckton)
Last Presented: (none)
For more information see the TC39 proposal process.
- Ron Buckton (@rbuckton)
The ECMAScript Decorators proposal introduced the ability to "decorate" classes and class methods and fields by
marking those declarations with an @
-prefixed expression. These decorators could then evaluate user-defined code
during the evaluation of the class definition to perform various metaprogramming tasks, such as constructor
registration, input validation, logging and tracing, reflection, metadata, and more. However, decorators are currently
limited to classes and class elements, despite those declarations sharing many characteristics with other declarations
such as function
expressions and declarations, arrow functions, and object literal methods.
While "decorating" a function could be trivially accomplished via a function call — dec(function() {})
rather
than @dec function() {}
— such "decorators" do not have the same benefits that decorators on classes and
class elements are afforded. Class and class element decorators receive a context
object with additional information
about the decorated element and can use that object to validate a decorator target (i.e., "this decorator is only
allowed on a field"), attach metadata, and perform post-decoration registration. These decorators can also choose to
elide a return value to avoid wrapping/replacing the element. If we only support decorators on classes and class
elements, it becomes more difficult to write reusable decorators that can also work with dec(function() {})
-style
decoration as well due to the lack of a context
.
First-class function decorator support would make it far easier to write reusable decorators and would extend the same metaprogramming flexibility to more declarations, which makes the entire decorators feature more broadly supported and consistent throughout the language.
Many of the same use cases that apply to class and class element decorators apply to functions and object literal elements:
- Logging/Tracing/auditing (i.e.,
@Audit() function login(username, passwordHash) { ... }
) - Authorization (i.e.,
@Authorize("Administrators") function createUser() { ... }
) - HTTP/REST API Routing (i.e.,
@Get("/posts/:id") function getUser(id) { ... }
) - Testing/Registration (i.e.,
@Test() function userIsCreated() { ... }
) - Metadata (i.e.,
@ReturnType(() => Number) function add(x, y) { ... }
) - Generator Trampolines (i.e.,
@DataFlow() function* extractTransformAndLoad(sources) { ... }
)
In addition, by allowing decorators on functions, we can more easily write decorators themselves:
/** A decorator that wraps another decorator with a check to validate the decorated element. */
function AllowedTargets(kinds) {
const formatter = new Intl.ListFormat("en", { style: "long", type: "disjunction" });
return function (outerTarget, outerContext) {
if (outerTarget.kind !== "function") throw new TypeError("@AllowedTargets is only valid on a function");
return function (innerTarget, innerContext) {
if (!kinds.includes(innerContext.kind)) {
throw new TypeError(`@${outerContext.name} is only valid on a ${formatter.format(kinds)}`);
}
return outerTarget.call(this, innerTarget, innerContext);
};
};
}
@AllowedTargets(["class", "function"])
function ClassOrFunctionDecorator(target, context) { ... }
To improve consistency for decorator support within the language and to support these use cases we are proposing the adoption of the following capabilities:
- Support for
@decorator
syntax on:- Arrow Functions and Async Arrow Functions, both with and without parenthesis (i.e.,
@dec () => ...
,@dec x => ...
) - Function Expressions (including async functions and generators)
- Function Declarations (including async functions and generators)
- Object Literal Methods (including async methods and generators)
- Object Literal Getters and Setters
- Object Literal Property Assignments (including shorthand assignments)
- Arrow Functions and Async Arrow Functions, both with and without parenthesis (i.e.,
- Support for
accessor
syntax on Object Literal Property Assignments (including shorthand assignments)
- ECMAScript
- C# 10.0
- Python
Function Decorators use the same syntax as class and method decorators, except that they may be placed preceding the
leading function
, async
, or export
keywords of a FunctionExpression or FunctionDeclaration, before the
ArrowParameters of an ArrowFunction, or before the async
keyword of an AsyncArrowFunction:
// logging/tracing
@logged
function doWork() { ... }
// utility wrappers
const onpress = @debounce(250) (e) => console.log("button pressed: ", e.pressed);
// metadata
@ParamTypes(() => [Number, Number])
@ReturnType(() => Number)
function add(x, y) { return x + y; }
// React Functional Components
@withStyles({
root: { border: '1px solid black' },
})
@React.forwardRef
function Button(props, forwardedRef) {
...
}
Decorators on function expressions and arrow functions are applied before any reference is returned to the containing expression. Decorators have the opportunity to augment the function by attaching properties or metadata, wrap or replace the function with another function, or add "initializers" that are evaluated once decoration application is complete to perform registration using the final reference for the function.
Decorators on function declarations should have the same capabilities as those on function expressions, but with an important caveat. In ECMAScript, function declarations "hoist" — both their name and value are evaluated at the top of the containing Block, which allows them to be invoked prior to their actual declaration:
const a = foo(); // this is ok because `foo` is "hoisted" above this line.
function foo() { return 1; }
This is acceptable for function declarations because "hoisting" the value doesn't involve any code execution. However, decorators require execution before the value is accessible. As a result, we must somehow handle decorator application before the function itself can be referenced.
Over the years we have discussed how to address this both in and out of the TC39 plenary. In the end, we are left with one of five options:
- Introduce a pre-Evaluation step
- Dynamically apply decorators
- ✨ Do not hoist decorated functions
- Do not allow decorators on function declarations
- Only allow decorators on marked function declarations
✨ — Approach favored by the proposal champion.
In this approach, function declarations continue to be hoisted. Before any statements in the containing Script, Module, FunctionBody, or Block are evaluated, we would first evaluate and apply all decorators. This has several inherent issues that may make this nonviable, however.
The decorators themselves must either also be function declarations, or be imported from another file (with no circular import relationship between the files):
const dec = (target, context) => {};
@dec // errors because `@dec` ends up evaluated before `const dec` is initialized.
function foo() {}
Also, arguments to decorator factories cannot reference any variables in scope that are not imported from another file (again, with no circularities in the import graph). This would make it impossible for decorators to reference constants declared in the same file, which would be useful when decorating multiple functions with the same base data:
const BASE = "/api/users";
@route("PUT", `${BASE}/create`) // errors because BASE is not initialized when `@route` is evaluated.
export function createUser(data) { }
@route("GET", `${BASE}/:id`)
export function getUser(id) { }
Finally, decorator evaluation order becomes harder to reason over. With class and class element decorators, the decorator expressions are evaluated in document order. If decorated function declarations hoist, then those decorators' expressions would no longer evaluate in document order, as they too would be hoisted to the top of the block.
All of these cases differ from how decorators are applied to class
declarations, which results in inconsistencies
that are likely to trip up users. As a result, this approach is not recommended.
In this approach, decorators would be evaluated and applied either when execution would reach the declaration of the function, or when the binding for the function is first accessed:
f; // f is accessed, so f's decorators are applied here
@dec
function f() {} // execution reaches f, but nothing happens as its decorators are already applied
@dec
function g() {} // execution reaches g, so g's decorators are applied
This approach also has major inherent issues. As with Option 1, decorator expression evaluation is no longer in document order. Even worse, decorator evaluation order would now potentially be nondeterministic as different code paths could touch functions in different orders depending on any number of variable conditions.
In addition, its fairly common to have function declarations that are never encountered during normal code execution:
function factory() {
return {
getF() { return f; },
getG() { return g; }
};
@dec
function f() {}
@dec
function g() {}
}
In the above listing, execution will never reach the declarations of f
or g
due to the return
, and user code might
never invoke getF()
or getG()
on the result, so it is possible that neither f
nor g
will ever have its
decorators applied. This is a significant issue as decorators can be used for registration purposes, such as
registering custom DOM elements, tests, etc., and if there are cases where a decorated function declaration never has
its decorators evaluated it can make an entire program fail in unpredictable ways that are hard to diagnose. As a
result, this approach would encourage users to manually "hoist" their decorated function declarations to avoid these
pitfalls.
While this approach is quite similar to Option 3, we currently do not recommend this approach due to the non-determinism and runtime complexity that would be introduced by having to test whether any variable access is the first access to a decorated function.
In this approach, function declarations that are decorated do not hoist their values to the top of the block. Instead,
they would behave more like a let
or var
declaration initialized to a function expression:
@dec
function f() {}
// is essentially the same as
var f = @dec function() {};
Much like Option 2, this has the caveat that decorated function declarations would need to be manually "hoisted" above any code that uses them. However, unlike Option 2 there is no need for runtimes to introduce suboptimal first-use checks for variable references.
It should be noted that this approach introduces a potential refactoring hazard as existing codebases move to adopt decorators on function declarations, but this can be partially remediated by linters and type systems.
Despite these caveats, this approach has the benefit that decorator expression evaluation remains in document order and is completely aligned with class and class element decorators, which would eliminate the guesswork that Options 1 and 2 would introduce.
This is the approach currently favored by the proposal champions as it provides the most consistent semantics.
This is by far the simplest option, as it avoids the problem entirely. However, disallowing decorators on function declarations would be inconsistent with the rest of this proposal and would not grant function declarations the benefits offered by decorators as indicated in Overview and Motivations. As a result, this approach is not recommended by the proposal champions.
This approach is similar to Option 4, in that normal
function declarations cannot be decorated, but also a variant of Option 3
in which decorated function declarations are not hoisted. Instead of relying on the presense of a decorator to act as a
syntactic opt-in for Option 3's hoisting behavior, this approach would require an additional syntactic opt-in in the
form of a prefix keyword, such as var
, let
, or const
:
function fn() {} // hoisted as normal, cannot be decorated
@dec
var function fnv() {}
// equiv. to:
var fnv = @dec function() {};
@dec
let function fnl() {}
// equiv. to:
let fnl = @dec function() {};
@dec
const function fnc() {}
// equiv. to:
const fnc = @dec function() {};
While this is an intriguing approach, its possible that some other future proposal might be better served by
const function
, such as indicating that a function performs no mutations, so we are reticent to recommend this
approach.
Decorators on object literal elements would behave much like their counterparts on classes and class elements. In
addition, this proposal seeks to extend the accessor
keyword from class fields to be used in conjunction with property
assignments and shorthand property assignments:
const y = 2;
const obj = {
accessor x: 1, // auto-accessor over a property assignment
accessor y, // auto-accessor over a shorthand property assignment
};
const desc = Object.getOwnPropertyDescriptor(obj, "x");
typeof desc.get; // "function"
typeof desc.set; // "function"
By supporting accessor
we would promote reuse of class auto-accessor decorators on object literals.
Function decorators would be evaluated in document order, as with any other decorators. Function decorators do not have access to the local scope within the function body, as they are evaluated in the lexical environment that contains the function instead.
For example, given the source
@A
@B
function foo() {}
the decorator expressions would be evaluated in the following order: A
, B
.
Function decorators are applied in reverse order, in keeping with decorator evaluation elsewhere within the language.
For example, given the source
@A
@B
function foo() {
}
decorators would be applied in the following order:
B
,A
offunction foo()
Much like class decorators, function decorators may define metadata which is then installed on the function itself:
const meta = (k, v) => (_, context) => { context.metadata[k] = v; };
@meta("foo", "bar")
function baz() {}
console.log(baz[Symbol.metadata]["foo"]); // prints: bar
Currently, the context.metadata
property provided to class method decorators is installed on the containing class. As
a result, class methods cannot define metadata that is instead defined on the method itself. This proposal does not seek
to change context.metadata
, but may choose to seek the addition of a context.functionMetadata
or
context.function.metadata
(or similar) property to allow for metadata defined on the method instead.
Object literal element decorators would behave similarly to class element decorators in that the context.metadata
object would be installed on the object literal itself so that all class element decorators can share the same metadata
object, promoting reuse of class method decorators. For consistency with function decorators, we may also seek the
addition of a context.functionMetadata
(or similar) property for methods requiring function-specific metadata.
PropertyDefinition[Yield, Await] :
- IdentifierReference[?Yield, ?Await]
- CoverInitializedName[?Yield, ?Await]
- PropertyName[?Yield, ?Await] `:` AssignmentExpression[+In, ?Yield, ?Await]
- MethodDefinition[?Yield, ?Await]
+ DecoratorList[?Yield, ?Await]? IdentifierReference[?Yield, ?Await]
+ DecoratorList[?Yield, ?Await]? CoverInitializedName[?Yield, ?Await]
+ DecoratorList[?Yield, ?Await]? `accessor` IdentifierReference[?Yield, ?Await]
+ DecoratorList[?Yield, ?Await]? PropertyName[?Yield, ?Await] `:` AssignmentExpression[+In, ?Yield, ?Await]
+ DecoratorList[?Yield, ?Await]? `accessor` PropertyName[?Yield, ?Await] `:` AssignmentExpression[+In, ?Yield, ?Await]
+ DecoratorList[?Yield, ?Await]? MethodDefinition[?Yield, ?Await]
`...` AssignmentExpression[+In, ?Yield, ?Await]
# Function Declarations and Expressions
FunctionDeclaration[Yield, Await, Default] :
- `function` BindingIdentifier[?Yield, ?Await] `(` FormalParameters[~Yield, ~Await] `)` `{` FunctionBody[~Yield, ~Await] `}`
- [+Default] `function` `(` FormalParameters[~Yield, ~Await] `)` `{` FunctionBody[~Yield, ~Await] `}`
+ DecoratorList[?Yield, ?Await]? `function` BindingIdentifier[?Yield, ?Await] `(` FormalParameters[~Yield, ~Await] `)` `{` FunctionBody[~Yield, ~Await] `}`
+ [+Default] DecoratorList[?Yield, ?Await]? `function` `(` FormalParameters[~Yield, ~Await] `)` `{` FunctionBody[~Yield, ~Await] `}`
- FunctionExpression :
- `function` BindingIdentifier[~Yield, ~Await]? `(` FormalParameters[~Yield, ~Await] `)` `{` FunctionBody[~Yield, ~Await] `}`
+ FunctionExpression[Yield, Await] :
+ DecoratorList[?Yield, ?Await]? `function` BindingIdentifier[~Yield, ~Await]? `(` FormalParameters[~Yield, ~Await] `)` `{` FunctionBody[~Yield, ~Await] `}`
ArrowFunction[In, Yield, Await] :
- ArrowParameters[?Yield, ?Await] [no LineTerminator here] `=>` ConciseBody[?In]
+ DecoratorList[?Yield, ?Await]? ArrowParameters[?Yield, ?Await] [no LineTerminator here] `=>` ConciseBody[?In]
AsyncArrowFunction[In, Yield, Await] :
- `async` [no LineTerminator here] AsyncArrowBindingIdentifier[?Yield] [no LineTerminator here] `=>` AsyncConciseBody[?In]
- CoverCallExpressionAndAsyncArrowHead[?Yield, ?Await] [no LineTerminator here] `=>` AsyncConciseBody[?In]
+ DecoratorList[?Yield, ?Await]? `async` [no LineTerminator here] AsyncArrowBindingIdentifier[?Yield] [no LineTerminator here] `=>` AsyncConciseBody[?In]
+ DecoratorList[?Yield, ?Await]? CoverCallExpressionAndAsyncArrowHead[?Yield, ?Await] [no LineTerminator here] `=>` AsyncConciseBody[?In]
GeneratorDeclaration[Yield, Await, Default] :
- `function` `*` BindingIdentifier[?Yield, ?Await] `(` FormalParameters[+Yield, ~Await] `)` `{` GeneratorBody `}`
- [+Default] `function` `*` `(` FormalParameters[+Yield, ~Await] `)` `{` GeneratorBody `}`
+ DecoratorList[?Yield, ?Await]? `function` `*` BindingIdentifier[?Yield, ?Await] `(` FormalParameters[+Yield, ~Await] `)` `{` GeneratorBody `}`
+ [+Default] DecoratorList[?Yield, ?Await]? `function` `*` `(` FormalParameters[+Yield, ~Await] `)` `{` GeneratorBody `}`
- GeneratorExpression :
- `function` `*` BindingIdentifier[+Yield, ~Await]? `(` FormalParameters[+Yield, ~Await] `)` `{` GeneratorBody `}`
+ GeneratorExpression[Yield, Await] :
+ DecoratorList[?Yield, ?Await]? `function` `*` BindingIdentifier[+Yield, ~Await]? `(` FormalParameters[+Yield, ~Await] `)` `{` GeneratorBody `}`
AsyncGeneratorDeclaration[Yield, Await, Default] :
- `async` [no LineTerminator here] `function` `*` BindingIdentifier[?Yield, ?Await] `(` FormalParameters[+Yield, +Await] `)` `{` AsyncGeneratorBody `}`
- [+Default] `async` [no LineTerminator here] `function` `*` `(` FormalParameters[+Yield, +Await] `)` `{` AsyncGeneratorBody `}`
+ DecoratorList[?Yield, ?Await]? `async` [no LineTerminator here] `function` `*` BindingIdentifier[?Yield, ?Await] `(` FormalParameters[+Yield, +Await] `)` `{` AsyncGeneratorBody `}`
+ [+Default] DecoratorList[?Yield, ?Await]? `async` [no LineTerminator here] `function` `*` `(` FormalParameters[+Yield, +Await] `)` `{` AsyncGeneratorBody `}`
- AsyncGeneratorExpression :
- `async` [no LineTerminator here] `function` `*` BindingIdentifier[+Yield, +Await]? `(` FormalParameters[+Yield, +Await] `)` `{` AsyncGeneratorBody `}`
+ AsyncGeneratorExpression[Yield, Await] :
+ DecoratorList[?Yield, ?Await]? `async` [no LineTerminator here] `function` `*` BindingIdentifier[+Yield, +Await]? `(` FormalParameters[+Yield, +Await] `)` `{` AsyncGeneratorBody `}`
AsyncFunctionDeclaration[Yield, Await, Default] :
- `async` [no LineTerminator here] `function` BindingIdentifier[?Yield, ?Await] `(` FormalParameters[~Yield, +Await] `)` `{` AsyncFunctionBody `}`
- [+Default] `async` [no LineTerminator here] `function` `(` FormalParameters[~Yield, +Await] `)` `{` AsyncFunctionBody `}`
+ DecoratorList[?Yield, ?Await]? `async` [no LineTerminator here] `function` BindingIdentifier[?Yield, ?Await] `(` FormalParameters[~Yield, +Await] `)` `{` AsyncFunctionBody `}`
+ [+Default] DecoratorList[?Yield, ?Await]? `async` [no LineTerminator here] `function` `(` FormalParameters[~Yield, +Await] `)` `{` AsyncFunctionBody `}`
- AsyncFunctionExpression :
- `async` [no LineTerminator here] `function` BindingIdentifier[~Yield, +Await]? `(` FormalParameters[~Yield, +Await] `)` `{` AsyncFunctionBody `}`
+ AsyncFunctionExpression[Yield, Await] :
+ DecoratorList[?Yield, ?Await]? `async` [no LineTerminator here] `function` BindingIdentifier[~Yield, +Await]? `(` FormalParameters[~Yield, +Await] `)` `{` AsyncFunctionBody `}`
The API for the decorators introduced in this proposal is consistent with the Decorators proposal. A given decorator
will be called by the runtime with two arguments, target
and context
, whose values are dependent on the element
being decorated. The return values of these decorators potentially replaces all or part of the decorated element.
A function decorator is expected to accept two parameters: target
and context
. Much like a class or class method
decorator, the target
argument will be the decorated function.
The context
for a function decorator would contain useful information about the function:
type FunctionDecoratorContext = {
kind: "function";
name: string | symbol | undefined;
metadata: object;
addInitializer(initializer: () => void): void;
}
kind
— Indicates the kind of element being decorated.name
— The name of the function. The name can potentially be a symbol due to assigned names from property assignments.metadata
— In keeping with the Decorator Metadata proposal, you would be able to attach metadata to the function.addInitializer
— This would allow you to attach an extra initalizer that runs after decorator application, much like you could for a decorator on a class method.
Returning a function from this decorator will replace the target
with that function, returning undefined
will leave
target
unchanged, and returning anything else is an error.
An object literal method decorator behaves much like a class method decorator, where its target
is the method being
decorated.
The context
for a object literal method decorator would contain useful information about the method:
type ObjectLiteralMethodDecoratorContext = {
kind: "object-method"; // or maybe just "method" since they are similar
name: string | symbol;
private: false;
static: false;
metadata: object;
functionMetadata: object; // (if we opt to allow metadata for the function itself)
addInitializer(initializer: () => void): void;
}
kind
— Indicates the kind of element being decorated.name
— The name of the method.private
— Whether the element has a private name. Currently for object literal methods this is alwaysfalse
.static
— Whether the element is declaredstatic
. Currently for object literal methods this is alwaysfalse
.- NOTE: This is up for debate as it may be that this should be
true
since there is no per-instance evaluation to consider.
- NOTE: This is up for debate as it may be that this should be
metadata
— In keeping with the Decorator Metadata proposal, you would be able to attach metadata to the object containing this method.functionMetadata
— If we opt to allow per-function metadata, this would be the unique object installed on the method.addInitializer
— This would allow you to attach an extra initalizer that runs after decorator application, much like you could for a decorator on a class method.
The decorator contexts for getters and setters would behave similarly:
type ObjectLiteralGetterDecoratorContext = {
kind: "object-getter"; // or just "getter"?
... // other properties from ObjectLiteralMethodDecoratorContext
}
type ObjectLiteralSetterDecoratorContext = {
kind: "object-setter"; // or just "setter"?
... // other properties from ObjectLiteralMethodDecoratorContext
}
Returning a function from this decorator will replace the target
with that function, returning undefined
will leave
target
unchanged, and returning anything else is an error.
Property assigment (and shorthand property assignment) decorators behave much like class field decorators, where the
target
is always undefined
.
The context
for an object literal property assignment decorator would contain useful information about the property:
type ObjectLiteralPropertyDecoratorContext = {
kind: "object-property"; // or just "property"
name: string | symbol;
private: false;
static: false;
metadata: object;
addInitializer(initializer: () => void): void;
}
kind
— Indicates the kind of element being decorated.name
— The name of the property.private
— Whether the element has a private name. Currently for object literal properties this is alwaysfalse
.static
— Whether the element is declaredstatic
. Currently for object literal properties this is alwaysfalse
.- NOTE: This is up for debate as it may be that this should be
true
since there is no per-instance evaluation to consider.
- NOTE: This is up for debate as it may be that this should be
metadata
— In keeping with the Decorator Metadata proposal, you would be able to attach metadata to the object containing this property.addInitializer
— This would allow you to attach an extra initalizer that runs after decorator application, much like you could for a decorator on a class field.
Returning a function from this decorator would chain a new initializer mutator, much like a class field decorator:
const addOne = (_target, _context) => x => x + 1;
const obj = {
@addOne x: 2,
};
console.log(obj.x); // 3
Returning undefined
will leave the initializer mutator chain unchanged, and returning anything else is an error.
Auto-accessor property assigment (and shorthand property assignment) decorators behave much like class auto-accessor
decorators, where the target
is a { get, set }
object pointing to the current getter and setter for the
auto-accessor.
The context
for an object literal auto-accessor decorator would contain useful information about the property:
type ObjectLiteralAutoAccessorDecoratorContext = {
kind: "object-accessor"; // or maybe just "accessor" since they are similar
name: string | symbol;
private: false;
static: false;
metadata: object;
accessorMetadata: { get: object, set: object }; // (if we opt to allow metadata for the functions themselves)
addInitializer(initializer: () => void): void;
}
kind
— Indicates the kind of element being decorated.name
— The name of the element.private
— Whether the element has a private name. Currently for object literal elements this is alwaysfalse
.static
— Whether the element is declaredstatic
. Currently for object literal elements this is alwaysfalse
.- NOTE: This is up for debate as it may be that this should be
true
since there is no per-instance evaluation to consider.
- NOTE: This is up for debate as it may be that this should be
metadata
— In keeping with the Decorator Metadata proposal, you would be able to attach metadata to the object containing this element.addInitializer
— This would allow you to attach an extra initalizer that runs after decorator application, much like you could for a decorator on a class element.
Returning a { get, set, init }
object from this decorator would allow for replacement of the target getter or setter,
or chain a new initializer mutator, much like a class auto-accessor decorator:
const addOne = (_target, _context) => ({ init: x => x + 1 });
const obj = {
@addOne accessor x: 2,
};
console.log(obj.x); // 3
Returning undefined
will leave the target getter, setter, and initializer mutator chain unchanged. Returning
undefined
for any of the get
, set
, or init
properties will leave the getter, setter, or initializer mutator
chain unchanged, respectively. Returning anything else is an error.
function debounce(timeout) {
return function (target, context) {
switch (context.kind) {
case "method":
case "object-method":
case "function":
break;
default:
throw new Error(`Not supported for kind: ${context.kind}`);
}
let timer;
let deferred;
function run(thisArg, args) {
if (timer) {
clearTimeout(timer);
timer = undefined;
}
const { resolve, reject } = deferred;
deferred = undefined;
try {
resolve(target.apply(thisArg, args));
}
catch (e) {
reject(e);
}
}
return function (...args) {
if (timer) {
clearTimeout(timer);
timer = undefined;
}
deferred ??= Promise.withResolvers();
timer = setTimeout(() => run(this, args), timeout);
return deferred.promise;
};
}
}
obj.on("change", @debounce(100) e => { ... });
function retry({ maxAttempts, shouldRetry }) {
return function (target, context) {
switch (context.kind) {
case "method":
case "object-method":
case "function":
break;
default:
throw new Error(`Not supported for kind: ${context.kind}`);
}
return async function (...args) {
for (let i = maxAttempts; i > 1; i- ) {
try {
return await target.apply(this, args);
}
catch (e) {
if (!shouldRetry || shouldRetry(e)) continue;
throw e;
}
}
return await target.apply(this, args);
}
}
}
@retry({ maxAttempts: 3, shouldRetry: e => e instanceof IOError })
export async function downloadFile(url) { ... }
NOTE: This example leverages the AsyncContext proposal.
const authVar = new AsyncContext.Variable();
function auth(role) {
return function (target, context) {
switch (context.kind) {
case "method":
case "object-method":
case "function":
break;
default:
throw new Error(`Not supported for kind: ${context.kind}`);
}
return function (...args) {
const user = authVar.get();
if (!user) throw new Error("Not authenticated");
if (!user.isInRole(role)) throw new Error("Not authorized");
return target.apply(this, args);
};
};
}
export function runAs(user, cb) {
return authVar.run(user, cb);
}
@auth("Administrator")
export async function createUser(newUser) { ... }
In this example, a multi-user web application would establish the current user context via a call to runAs
. Within the
callback, if createUser
is invoked then the user associated with the current AsyncContext.Variable
is retrieved and
access is checked before the function body itself can be invoked.
These examples derived from AWS's Chalice, a Python library for Amazon Web Services. These examples involve concepts such as attaching metadata and registration.
import { Chalice } from "chalice";
const app = new Chalice({ appName: "helloworld" });
@app.route("/")
function index() { return { "hello": "world" }; }
import { Chalice, Rate } from "chalice";
const app = new Chalice({ appName: "helloworld" });
@app.schedule(new Rate(5, { unit: Rate.MINUTES }))
function periodicTask(event) { ... }
import { Chalice, Rate } from "chalice";
const app = new Chalice({ appName: "helloworld" });
@app.on_s3_event({ bucket: "mybucket" })
function handler(event) {
console.log(`Object uploaded for bucket: ${event.bucket}, key: ${event.key}`);
}
- Decorators (Stage 3)
- Decorator Metadata (Stage 3)
- Class Constructor and Method Parameter Decorators (Stage 1)
The following is a high-level list of tasks to progress through each stage of the TC39 proposal process:
- Identified a "champion" who will advance the addition.
- Prose outlining the problem or need and the general shape of a solution.
- Illustrative examples of usage.
- High-level API.
- Initial specification text.
- Transpiler support (Optional).
- Complete specification text.
- Designated reviewers have signed off on the current spec text:
- Reviewer #1 has signed off
- Reviewer #2 has signed off
- The ECMAScript editor has signed off on the current spec text.
- Two compatible implementations which pass the acceptance tests: [1], [2].
- A pull request has been sent to tc39/ecma262 with the integrated spec text.
- The ECMAScript editor has signed off on the pull request.