cf.completefrontend
Open lab My progress
THE JAVASCRIPT FIELD GUIDE

On the horizon

Learn which JavaScript proposals are close: Temporal and using for ES2027, decorators, the pipeline operator, pattern matching, signals and more.

By the end, you can
  • 01
    Know what is comingSay what Temporal, using, decorators, the pipeline operator, pattern matching and Signals are for.
  • 02
    Judge how close each one isRead a proposal's stage and decide whether to use it, try it with a compiler, or just follow it.
  • 03
    Do the job todayWrite today's version of each idea: try/finally, wrapper functions, named steps, switch and JSDoc.

On the horizon: proposals worth watching

Some JavaScript features are almost here, and others are still ideas. This lesson looks at the proposals people talk about most: what each one is for, how close it is, and how you do the same job today. The stages come from TC39's list of proposals in October 2026, and they change over time.

Which proposals to try now, and which to watchPop out in the code editor (opens in a new tab)JavaScript
const proposals = [
  { name: "Temporal", stage: 4 },
  { name: "Decorators", stage: 2.7 },
  { name: "Pipeline operator", stage: 2 },
  { name: "Pattern matching", stage: 1 },
];
for (const { name, stage } of proposals) {
  console.log(name + ": " + (stage >= 3 ? "try it now" : "watch it"));
}

Lines 1 to 6 list four proposals with their stages. Lines 7 to 9 print advice for each one: try it now from stage 3, when browsers start building a proposal, and watch it before that.

So it prints Temporal: try it now, then watch it for decorators, the pipeline operator and pattern matching. The how JavaScript evolves lesson explains the stages.

Five columns, from stage 1 on the left to stage 4 on the right. Stage 1: match, Signals, types. Stage 2: pipeline, AsyncContext, structs. Stage 2.7: decorators, ShadowRealm. Stage 3: import defer, source phase. Stage 4: Temporal, using. An arrow along the bottom points right, labeled closer to your browser.stage 1matchSignalstypesstage 2pipelineAsyncContextstructsstage 2.7decoratorsShadowRealmstage 3import defersource phasestage 4Temporalusingcloser to your browser
The proposals in this lesson, by their stage in October 2026. The further right, the closer a proposal is to your browser. The example prints try it now only for stage 3 and up.

Read each row of the table as: "this proposal is at this stage, does this, and you can try it like this."

The proposals in this lesson, in October 2026
The proposalStageWhat it is forHow to try it today
Temporal4Dates and times without Date's surprises.Built into the newest browsers, or a polyfill such as @js-temporal/polyfill.
using4Cleans up a resource when its block ends.Built into the newest browsers, and TypeScript 5.2 and later.
import defer3Loads a module now, but runs it only when it is first used.Babel's import-defer plugin.
Decorators2.7Wraps a class or method with extra behavior.TypeScript 5.0 and later, or Babel's decorators plugin.
ShadowRealm2.7Runs code with its own fresh set of built-ins.Not yet. Use an iframe or a worker for now.
Pipeline |>2Writes steps in the order they happen.Babel's pipeline-operator plugin, for experiments.
AsyncContext2Keeps a value across await without passing it along.Node.js's AsyncLocalStorage does a similar job.
Structs2Objects with a fixed shape that threads can share.Object.seal gives a fixed shape today.
Pattern matching1Compares a value against shapes, like a stronger switch.Not yet. Use switch or if.
Signals1Values that tell other code when they change.The signal-polyfill package, or a framework's own signals.
Type annotations1Types that the engine skips, like comments.TypeScript, or JSDoc comments.
Real-life analogyNew dishes at a restaurant

Picture a restaurant. Some dishes are on the menu and you can order them today. Some are specials the kitchen is testing. Some recipes are still changing every week, and some are only notes in the chef's notebook. You wouldn't plan a party around a notebook idea, but you might ask for a taste of a special.

In real life: Dishes on today's menu
In JavaScript: Finished features, such as Temporal
In real life: Specials the kitchen is testing
In JavaScript: Stage 2.7 and 3 proposals
In real life: Recipes the chef is still changing
In JavaScript: Stage 2 proposals
In real life: Ideas in the chef's notebook
In JavaScript: Stage 1 proposals
The main idea

A proposal's stage tells you how close it is. Use finished features, try stage 2.7 and 3 proposals with a compiler or polyfill, and only watch stage 1 and 2 ideas. For each one, there is a way to do the same job today.

Finished for ES2027: Temporal and using

Two big proposals reached stage 4 and are expected in ES2027. The newest browsers already have both.

Temporal is a new way to work with dates and times. It fixes many of Date's surprises, such as months that start at 0.

A Date surprise: months start at 0Pop out in the code editor (opens in a new tab)JavaScript
const day = new Date(2026, 9, 13);
console.log(day.getMonth());
day.setDate(day.getDate() + 30);
console.log(day.getMonth() + 1, day.getDate());

Line 1 makes a date for 13 October 2026. Date counts months from 0, so October is written 9, and line 2 prints 9.

Line 3 adds 30 days by changing the day of the month, which rolls over into November. Line 4 adds 1 to the month to get a normal number, and prints 11 12.

The same dates with TemporalPop out in the code editor (opens in a new tab)JavaScript
const today = Temporal.PlainDate.from("2026-10-13");
const due = today.add({ days: 30 });
console.log(today.month, due.toString());
console.log(today.until(due).days);
console.log(Temporal.PlainDate.compare(today, due));

Line 1 makes a PlainDate, a date with no time and no time zone. Line 2 adds 30 days and gives a new date; the first one doesn't change. Line 3 prints 10 2026-11-12: Temporal counts months from 1.

Line 4 asks how far apart they are, and prints 30. Line 5 compares them and prints -1, meaning the first comes before the second.

Two rows. In the first, new Date(2026, 9, 13) points to a card reading month 9, labeled counts from 0. In the second, Temporal.PlainDate.from of 2026-10-13 points to a card reading month 10, labeled counts from 1. Both mean October.new Date(2026, 9, 13)month 9counts from 0PlainDate "2026-10-13"month 10counts from 1both are October
Both rows mean 13 October 2026. Date numbers months from 0, so October is 9. Temporal numbers them from 1, so October is 10, as on a calendar.

The second finished proposal is explicit resource management, written using. A resource is something that must be cleaned up after use, such as an open file or a connection. Today you clean up with try and finally.

Cleaning up today with try and finallyPop out in the code editor (opens in a new tab)JavaScript
function openCup(name) {
  console.log("open " + name);
  return {
    close() {
      console.log("wash " + name);
    },
  };
}
const cup = openCup("tea");
try {
  console.log("drink");
} finally {
  cup.close();
}
console.log("done");

Lines 1 to 8 make a pretend cup that must be washed. Line 9 opens it. Lines 10 to 14 drink from it, and finally washes it on line 13, even if something throws. It prints open tea, drink, wash tea, done.

The same cleanup with usingPop out in the code editor (opens in a new tab)JavaScript
function openCup(name) {
  console.log("open " + name);
  return {
    [Symbol.dispose]() {
      console.log("wash " + name);
    },
  };
}
{
  using cup = openCup("tea");
  console.log("drink");
}
console.log("done");

Lines 4 to 6 give the cup a method named Symbol.dispose. Line 10 declares the cup with using, which ties it to the block on lines 9 to 12.

When the block ends, JavaScript calls Symbol.dispose by itself, so this prints the same four lines, with no finally to forget.

A box labeled block holds a card using cup. Three steps run left to right: open tea, drink, then wash tea, labeled at the block's end.using cupblockopen teadrinkwash teaat the block's end
using on line 10 ties the cup to the block. When the block ends on line 12, JavaScript calls the cup's Symbol.dispose method, so wash tea prints before done.

Deferred and source phase imports: stage 3

Two stage 3 proposals change how modules are imported. With import defer, a module is loaded at the start but runs only when your code first uses it. That speeds up page start when some code is rarely needed.

import defer, as proposedJavaScript
import defer * as charts from "./charts.js";
button.addEventListener("click", () => {
  charts.draw();
});

Line 1 loads charts.js but doesn't run it. Line 3, on the first click, uses charts, and only then does charts.js run. No browser runs this yet, so this box has no Run button.

Source phase imports let you import a module's compiled form without running it, mostly for WebAssembly.

A source phase import, as proposedJavaScript
import source pricesModule from "./prices.wasm";
const prices = await WebAssembly.instantiate(pricesModule);

Line 1 imports the compiled WebAssembly module without starting it. Line 2 starts it when the code is ready.

Today, the closest tool is import(), which loads and runs a module only when you call it.

Loading a module only when needed, todayPop out in the code editor (opens in a new tab)JavaScript
async function showCharts() {
  const charts = await import("./charts.js");
  console.log(charts.draw());
}
console.log("page ready");
showCharts();

Lines 1 to 4 make a function that imports charts.js when called. Line 5 prints page ready first. Line 6 calls the function, so charts.js runs and prints charts.js runs, then line 3 prints chart drawn.

Decorators and ShadowRealm: stage 2.7

A decorator is a function you put in front of a class or method with @. It receives the method and can give back a new function to use instead, for example one that logs each call.

A logging decorator, as proposedJavaScript
function logged(method, context) {
  return function (...args) {
    console.log("calling " + context.name);
    return method.apply(this, args);
  };
}
class Cart {
  @logged
  total(price) {
    return price + 1;
  }
}
console.log(new Cart().total(3));

Lines 1 to 6 make logged. It receives the method and an object describing it, and returns a new function that prints the method's name and then calls the real method.

Line 8 puts @logged in front of total. Line 13 would print calling total, then 4. Browsers don't run decorators yet, but TypeScript and Babel turn them into plain JavaScript.

The same idea today: wrap the method by handPop out in the code editor (opens in a new tab)JavaScript
function logged(method, name) {
  return function (...args) {
    console.log("calling " + name);
    return method.apply(this, args);
  };
}
class Cart {
  total(price) {
    return price + 1;
  }
}
Cart.prototype.total = logged(Cart.prototype.total, "total");
console.log(new Cart().total(3));

Lines 1 to 6 make the same kind of wrapper. Line 12 replaces total on the class's prototype with the wrapped version. Line 13 prints calling total, then 4.

ShadowRealm, also at stage 2.7, runs code with its own fresh set of built-ins, such as its own Array, so code inside it can't change yours.

A ShadowRealm, as proposedJavaScript
const realm = new ShadowRealm();
const double = realm.evaluate("(n) => n * 2");
console.log(double(3));

Line 1 makes a new realm. Line 2 builds a function inside it from text. Line 3 calls it from your code and would print 6. Today, an iframe or a worker gives a similar separation.

The pipeline operator, AsyncContext and structs: stage 2

At stage 2, the committee has chosen a design, but it can still change a lot. The pipeline operator, |>, passes a value through steps in the order they happen. In the current draft, % stands for the value from the step before.

A pipeline, as proposedJavaScript
const label = prices
  |> %.filter((p) => p < 5)
  |> sum(%)
  |> `Total: ${%}`;

Line 1 starts with prices. Line 2 keeps the ones under 5, line 3 adds them up, and line 4 makes the label. You read it top to bottom, in the same order the work is done.

The same steps todayPop out in the code editor (opens in a new tab)JavaScript
const prices = [3, 4, 5];
const sum = (list) => list.reduce((a, b) => a + b, 0);
console.log(`Total: ${sum(prices.filter((p) => p < 5))}`);
const cheap = prices.filter((p) => p < 5);
const total = sum(cheap);
console.log(`Total: ${total}`);

Line 3 does all three steps in one nested call, which you read from the inside out, and prints Total: 7. Lines 4 and 5 give each step a name instead, so line 6 prints Total: 7 too, and reads in order.

Four cards in a row joined by arrows: prices, filter under 5, sum, and Total: 7. Under the row: read in the order the steps happen.pricesfilter < 5sumTotal: 7read in the order the steps happen
A pipeline lists the steps in the order they happen: filter, then sum, then the label. The nested call on line 3 of the today example does the same steps, but you read it from the inside out.

AsyncContext keeps a value available across await without passing it to every function, for example the id of the order being handled.

AsyncContext, as proposedJavaScript
const orderId = new AsyncContext.Variable();
orderId.run("order-7", async () => {
  await brew();
  console.log(orderId.get());
});

Line 1 makes a variable. Line 2 runs some code with it set to "order-7". Even after the await on line 3, line 4 would still read order-7. Node.js has a similar tool today, AsyncLocalStorage.

Structs are objects with a fixed shape: you can't add properties later. Shared structs can also be used by several threads at once.

A struct, as proposedJavaScript
struct Point {
  x;
  y;
}
const point = new Point();
point.z = 1;

Lines 1 to 4 declare a struct with two fields. Line 6 tries to add a third, which the proposal makes an error.

A fixed shape today with Object.sealPop out in the code editor (opens in a new tab)JavaScript
const point = Object.seal({ x: 0, y: 0 });
point.x = 3;
point.z = 1;
console.log(point.x, point.z);
console.log(Object.keys(point).join(" "));

Line 1 seals an object: its properties can change, but none can be added. Line 2 works, and line 3 is silently ignored in sloppy code. Line 4 prints 3 undefined, and line 5 prints x y.

Pattern matching, Signals and type annotations: stage 1

At stage 1, the committee agrees the problem is worth solving, but the solution may change completely. Treat these as ideas to follow, not syntax to learn by heart.

Pattern matching compares a value against several shapes and picks the first that fits, like a stronger switch.

A pattern match, sketched from the stage 1 draftJavaScript
const message = match (status) {
  when "paid": "Thanks!";
  when "late": "Sorry for the wait";
  default: "Waiting";
};

Line 1 matches status. Lines 2 and 3 each check one value, and line 4 handles everything else. The whole match gives one value, which goes into message.

The same job today with switchPop out in the code editor (opens in a new tab)JavaScript
function messageFor(status) {
  switch (status) {
    case "paid":
      return "Thanks!";
    case "late":
      return "Sorry for the wait";
    default:
      return "Waiting";
  }
}
console.log(messageFor("late"), "/", messageFor("new"));

Lines 1 to 10 return a message for each status. Line 11 prints Sorry for the wait / Waiting.

Signals are values that tell other code when they change. Frameworks such as Angular, Solid and Vue already use the idea. The proposal would give them one shared, built-in version. Here is a small model of the idea, written for this lesson.

A small model of a signalPop out in the code editor (opens in a new tab)JavaScript
function createState(value) {
  const watchers = [];
  return {
    get: () => value,
    set(next) {
      value = next;
      watchers.forEach((run) => run());
    },
    watch: (run) => watchers.push(run),
  };
}
const price = createState(3);
price.watch(() => console.log("total is now " + (price.get() + 1)));
price.set(4);
price.set(5);

Lines 1 to 11 make createState, which keeps a value and a list of watchers. Line 7 runs every watcher whenever the value is set.

Line 12 makes a price of 3, and line 13 adds a watcher that prints the total. Lines 14 and 15 change the price, so it prints total is now 5 and total is now 6.

Step through a small model of a signal
Step 0 of 7Ready
Your turn: follow the blue line

A replay of the example code, not a view inside a JavaScript engine. createState is a small model of the idea behind Signals, not the proposal's own API.

Running in
  1. script
Next: line 1
  const watchers = [];  return {    get: () => value,    set(next) {      value = next;      watchers.forEach((run) => run());    },    watch: (run) => watchers.push(run),  };}const price = createState(3);price.watch(() => console.log("total is now " + (price.get() + 1)));price.set(4);price.set(5);
CallStoreChangeResultBlue line: runs next. Ran: already executed.
Recent returnsNothing yet. Start with the blue line.
A guided replay recorded from real JavaScript calls, not an engine debugger. Step follows executed statements; Back reviews a snapshot. Reset starts a fresh run.

Press Step and watch the price beside line 12 change, and the watcher run each time. This replays the example code; it isn't a view inside the JavaScript engine.

Type annotations would let you write types in JavaScript files, like TypeScript. Engines would skip them, the way they skip comments. They would not check them.

Type annotations, as proposedJavaScript
function total(price: number, tax: number): number {
  return price + tax;
}

Line 1 says that price and tax are numbers, and so is the result. Today this is a SyntaxError.

Types in JSDoc comments todayPop out in the code editor (opens in a new tab)JavaScript
/**
 * @param {number} price
 * @param {number} tax
 * @returns {number}
 */
function total(price, tax) {
  return price + tax;
}
console.log(total(3, 1));

Lines 1 to 5 are a JSDoc comment that describes the same types. Editors and TypeScript read it, and engines skip it. Line 9 prints 4.

Trying proposals safely

Before you use a proposal, ask two questions: what stage is it at, and do the browsers your users have support it? The playground answers the second question for the browser you are using right now.

Playground: is it in this browser, and should you use it?
Does this browser have Temporal?Pop out in the code editor (opens in a new tab)JavaScript
console.log(typeof Temporal === "undefined" ? "not built in" : "built in");
In this browserchecking…
TemporalOctober 2026
stage4
advicechecking…
Try it yourself
Temporal, stage 4.

Checking this browser…

The check runs for real in this browser, so the answer depends on which browser and version you use. The stages are from TC39's list in October 2026.

In a browser from 2026, Temporal and using show built in, and every other proposal shows not built in. In an older browser, all of them show not built in. The advice depends on the stage.

Which stage is each proposal at?
  • Temporal
  • using cup = openCup()
  • import defer * as charts from "./charts.js"
  • @logged on a method
  • new ShadowRealm()
  • prices |> sum(%)
  • new AsyncContext.Variable()
  • match (status) { … }
  • new Signal.State(3)
  • function total(price: number)
Try it yourself
0 of 10 correct

Put each proposal with its stage in October 2026: finished, stage 3 or 2.7, stage 2, or stage 1.

Choose a category for every card. You can change an answer at any time; Reset clears them all.

Some rules of thumb. Use a polyfill only for finished or stage 3 features. Use a compiler for stage 2.7 syntax if you can update your code when the proposal changes. For stage 1 and 2, try it in a small experiment, and read the proposal's repository to follow it.

Common mix-ups

Most mix-ups come from treating a proposal as finished because a tool already supports it.

  • "Decorators are part of JavaScript." No. They are at stage 2.7. TypeScript and Babel turn them into plain JavaScript.
  • "Type annotations will make JavaScript check types." No. Engines would skip them, like comments.
  • "Temporal is a library you install." No. It is finished, and the newest browsers have it built in. Polyfills are for older browsers.
  • "A stage 1 syntax is how it will look." No. It may change completely before it ships.
  • "import defer skips loading the module." No. It loads the module, but runs it only when it is first used.
  • "Signals are a framework." No. The proposal is a small built-in that frameworks could share.

The table below puts three of these side by side. Read each row as: "people often think this, but this is true."

What is true about three proposals
The proposalWhat people thinkWhat is true
DecoratorsThey are already part of JavaScript.They are at stage 2.7. TypeScript and Babel compile them away.
Type annotationsJavaScript will start checking types.The proposal makes engines skip types, not check them.
TemporalIt is a library you must install.It is finished, and the newest browsers have it built in.

Practice exercises

Each exercise uses today's tools for one of the ideas in this lesson. Guess first, then run it.

Exercise 1 · Warm-upPredict two Date surprises

What does line 1 print?

Starter codePop out in the code editor (opens in a new tab)JavaScript
console.log(new Date(2026, 0, 31).getMonth(), new Date(2026, 12, 1).getFullYear());

Answer, then press Check. Spacing and letter case don’t matter.

    Exercise 2 · Warm-upPredict the advice for two stages

    What does line 7 print?

    Starter codePop out in the code editor (opens in a new tab)JavaScript
    function advise(stage) {
      if (stage === 4) return "use it";
      if (stage >= 3) return "try it with a fallback";
      if (stage >= 2.7) return "try it with a compiler";
      return "watch it";
    }
    console.log(advise(2.7), "/", advise(2));

    Answer, then press Check. Spacing and letter case don’t matter.

      Exercise 3 · PracticeUnfold a nested call into named steps

      Rewrite line 4 as named steps, in the order they happen. What does your program print?

      Starter codePop out in the code editor (opens in a new tab)JavaScript
      const trim = (text) => text.trim();
      const upper = (text) => text.toUpperCase();
      const exclaim = (text) => text + "!";
      console.log(exclaim(upper(trim("  tea  "))));

      Answer, then press Check. Spacing and letter case don’t matter.

        Exercise 4 · PracticePredict the cleanup order

        What does the program print, in order?

        Starter codePop out in the code editor (opens in a new tab)JavaScript
        function openCup() {
          console.log("open");
          return { close: () => console.log("wash") };
        }
        const cup = openCup();
        try {
          console.log("drink");
          throw new Error("spilled");
        } catch (error) {
          console.log(error.message);
        } finally {
          cup.close();
        }

        Answer, then press Check. Spacing and letter case don’t matter.

          Exercise 5 · ChallengeWrite a counting wrapper, like a decorator

          Finish counted so that every call adds 1 to calls. What does your program print?

          Starter codePop out in the code editor (opens in a new tab)JavaScript
          function counted(method) {
            // Wrap method so every call adds 1 to calls.
            return method;
          }
          let calls = 0;
          class Cart {
            total(price) {
              return price + 1;
            }
          }
          Cart.prototype.total = counted(Cart.prototype.total);
          const cart = new Cart();
          cart.total(3);
          cart.total(4);
          console.log("calls: " + calls);

          Answer, then press Check. Spacing and letter case don’t matter.

            Exercise 6 · ChallengePredict two watchers on one state

            What does line 17 print?

            Starter codePop out in the code editor (opens in a new tab)JavaScript
            function createState(value) {
              const watchers = [];
              return {
                get: () => value,
                set(next) {
                  value = next;
                  watchers.forEach((run) => run());
                },
                watch: (run) => watchers.push(run),
              };
            }
            const cups = createState(1);
            const log = [];
            cups.watch(() => log.push("cups " + cups.get()));
            cups.watch(() => log.push("price " + cups.get() * 3));
            cups.set(2);
            console.log(log.join(", "));

            Answer, then press Check. Spacing and letter case don’t matter.

              Check your understanding

              For each question, ask what stage the proposal is at, and what today's way of doing the same job looks like. Each answer explains why it's right or wrong.

              The on the horizon quiz · 7 questionsScore: first tries count
              1. Question 1 of 7Which two proposals are finished and expected in ES2027?

                Choose an answer to see the explanation.

              2. Question 2 of 7What month number does Date give for October?

                Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
                console.log(new Date(2026, 9, 13).getMonth());

                Choose an answer to see the explanation.

              3. Question 3 of 7In what order does this try and finally code print?

                Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
                function open() {
                  console.log("open");
                  return { close: () => console.log("close") };
                }
                const cup = open();
                try {
                  console.log("use");
                } finally {
                  cup.close();
                }

                Choose an answer to see the explanation.

              4. Question 4 of 7What does a method decorator receive?

                Choose an answer to see the explanation.

              5. Question 5 of 7What does stage 1 mean for pattern matching?

                Choose an answer to see the explanation.

              6. Question 6 of 7How often does this simple watcher run when the same value is set twice?

                Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
                let value = 3;
                const watchers = [() => console.log("now " + value)];
                function set(next) {
                  value = next;
                  watchers.forEach((run) => run());
                }
                set(4);
                set(4);

                Choose an answer to see the explanation.

              7. Question 7 of 7Should you ship stage 2 syntax to users through a compiler?

                Choose an answer to see the explanation.

              Key takeaways

              New features keep coming. Knowing each one's stage tells you whether to use it, try it, or just watch it.

              • Temporal and using are finished and expected in ES2027; the newest browsers have both.
              • import defer and source phase imports are at stage 3.
              • Decorators and ShadowRealm are at stage 2.7. TypeScript and Babel can compile decorators today.
              • The pipeline operator, AsyncContext and structs are at stage 2.
              • Pattern matching, Signals and type annotations are at stage 1, and may change completely.
              • Every idea has a way to do the job today: finally, wrappers, named steps, switch and JSDoc.

              Remember the one-liner.
              Use what is finished, try what is close with a compiler, and watch the rest.

              Coming next: How new syntax is downleveled. It shows how tools like Babel turn tomorrow's syntax into code that today's engines can run.

              CompleteFrontend Clear concepts. Working examples.