Movatterモバイル変換


[0]ホーム

URL:


Skip to content
DEV Community
Log in Create account

DEV Community

Cover image for Upcoming JavaScript Features You Should Know About
Max Prilutskiy
Max Prilutskiy

Posted on

     

Upcoming JavaScript Features You Should Know About

After 20 years of writing JavaScript, I've seen many changes - from callback hell to async/await. But the upcoming JavaScript features will transform how we write code completely.

We've tested these proposals using transpilers and polyfills, ans the results are impressive: code that took 30 lines now takes 10, complex logic becomes readable at a glance, and even junior developers (and Devin 😅) can understand complex parts of our codebase.

If you've read my previous articles onHTML5 elements you didn't know you need orCSS modal windows, you know we like unusual tech atLingo.dev. These upcoming JavaScript features solve real problems that have annoyed developers for years.

Pipeline Operator Improves Code Readability

Complex data transformations in JavaScript often result in deeply nested function calls that are difficult to read and maintain. Developers must trace through nested functions from inside out, jumping between parentheses to understand the flow of data.

The pipeline operator (|>) solves this problem by allowing data to flow through a series of operations in a clear, top-to-bottom manner:

// Instead of this nested messconstresult=saveToDatabase(validateUser(normalizeData(enrichUserProfile(user))));// You'll write thisconstresult=user|>enrichUserProfile|>normalizeData|>validateUser|>saveToDatabase;
Enter fullscreen modeExit fullscreen mode

Let's look at a more complex real-world example. Consider an image processing service that has grown over time:

// Before: Nested function hellfunctionprocessImage(image){returncompressImage(addWatermark(optimizeForWeb(applyFilter(resizeImage(image,{width:800,height:600}),'sepia')),'Copyright 2025'),0.8);}// After: Clean, readable pipelinefunctionprocessImage(image){returnimage|>(img)=>resizeImage(img,{width:800,height:600})|>(img)=>applyFilter(img,'sepia')|>optimizeForWeb|>(img)=>addWatermark(img,'Copyright 2025')|>(img)=>compressImage(img,0.8);}
Enter fullscreen modeExit fullscreen mode

Here's another practical example showing how the pipeline operator simplifies data processing for analytics:

// Before: Hard to follow the data flowfunctionanalyzeUserData(users){returngenerateReport(groupByMetric(filterInactiveUsers(normalizeUserData(users)),'registrationMonth'));}// After: Clear data transformation stepsfunctionanalyzeUserData(users){returnusers|>normalizeUserData|>filterInactiveUsers|>(data)=>groupByMetric(data,'registrationMonth')|>generateReport;}
Enter fullscreen modeExit fullscreen mode

The pipeline operator also makes it easier to insert debugging or logging between steps:

functionprocessPayment(payment){returnpayment|>validatePayment|>(result)=>{console.log(`Validation result:${JSON.stringify(result)}`);returnresult;}|>processTransaction|>(result)=>{console.log(`Transaction result:${JSON.stringify(result)}`);returnresult;}|>sendReceipt;}
Enter fullscreen modeExit fullscreen mode

How to Use It Today

The pipeline operator is currently at Stage 2 in the TC39 process as of May 2025. While not yet part of the official JavaScript specification, you can start using it today:

With Babel (Generic Setup):

# Install the pipeline operator pluginnpminstall--save-dev @babel/plugin-proposal-pipeline-operator
Enter fullscreen modeExit fullscreen mode

Add to your.babelrc:

{"plugins":[["@babel/plugin-proposal-pipeline-operator",{"proposal":"hack","topicToken":"%"}]]}
Enter fullscreen modeExit fullscreen mode

With Vite:

# Install dependenciesnpminstall--save-dev vite-plugin-babel @babel/core @babel/plugin-proposal-pipeline-operator
Enter fullscreen modeExit fullscreen mode
// vite.config.jsimport{defineConfig}from'vite';importbabelfrom'vite-plugin-babel';exportdefaultdefineConfig({plugins:[babel({babelConfig:{plugins:[['@babel/plugin-proposal-pipeline-operator',{proposal:'hack',topicToken:'%'}]]}})]});
Enter fullscreen modeExit fullscreen mode

With Next.js:

# Install the pipeline operator pluginnpminstall--save-dev @babel/plugin-proposal-pipeline-operator
Enter fullscreen modeExit fullscreen mode
// .babelrc{"presets":["next/babel"],"plugins":[["@babel/plugin-proposal-pipeline-operator",{"proposal":"hack","topicToken":"%"}]]}
Enter fullscreen modeExit fullscreen mode

With tsup:

# Install dependenciesnpminstall--save-dev tsup @babel/core @babel/plugin-proposal-pipeline-operator
Enter fullscreen modeExit fullscreen mode
// tsup.config.tsimport{defineConfig}from'tsup';import*asbabelfrom'@babel/core';importfsfrom'fs';exportdefaultdefineConfig({entry:['src/index.ts'],format:['cjs','esm'],dts:true,esbuildPlugins:[{name:'babel',setup(build){build.onLoad({filter:/\.(jsx?|tsx?)$/},async(args)=>{constsource=awaitfs.promises.readFile(args.path,'utf8');constresult=awaitbabel.transformAsync(source,{filename:args.path,presets:[['@babel/preset-env',{targets:'defaults'}],'@babel/preset-typescript'],plugins:[['@babel/plugin-proposal-pipeline-operator',{proposal:'hack',topicToken:'%'}]]});return{contents:result?.code||'',loader:args.path.endsWith('x')?'jsx':'js'};});}}]});
Enter fullscreen modeExit fullscreen mode

With Remix (using Vite):

# Install dependenciesnpminstall--save-dev vite-plugin-babel @babel/core @babel/plugin-proposal-pipeline-operator
Enter fullscreen modeExit fullscreen mode
// vite.config.jsimport{defineConfig}from'vite';import{vitePluginasremix}from'@remix-run/dev';importbabelfrom'vite-plugin-babel';exportdefaultdefineConfig({plugins:[babel({babelConfig:{plugins:[['@babel/plugin-proposal-pipeline-operator',{proposal:'hack',topicToken:'%'}]]}}),remix()]});
Enter fullscreen modeExit fullscreen mode

Pattern Matching Simplifies Complex Conditionals

Complex if/else statements and switch cases quickly become unwieldy in large codebases. Functions with nested if/else blocks checking various object properties make it nearly impossible to verify all edge cases.

Pattern matching provides a direct solution to this problem.

This feature brings functional programming capabilities to JavaScript, allowing you to match and destructure complex data in a single operation:

functionprocessMessage(message){returnmatch(message){whenString(text)=>`Text message:${text}`,when[String(sender),String(content)]=>`Message from${sender}:${content}`,when{type:'error',content:String(text),code:Number(code)}=>`Error:${text} (Code:${code})`,when_=>'Unknown message format'};}
Enter fullscreen modeExit fullscreen mode

Without pattern matching, you'd need multiple if/else statements with type checking:

functionprocessMessage(message){// Check if it's a stringif(typeofmessage==='string'){return`Text message:${message}`;}// Check if it's an array with specific structureif(Array.isArray(message)&&message.length===2&&typeofmessage[0]==='string'&&typeofmessage[1]==='string'){return`Message from${message[0]}:${message[1]}`;}// Check if it's an object with specific propertiesif(message&&typeofmessage==='object'&&message.type==='error'&&typeofmessage.content==='string'&&typeofmessage.code==='number'){return`Error:${message.content} (Code:${message.code})`;}// Default casereturn'Unknown message format';}
Enter fullscreen modeExit fullscreen mode

Pattern matching excels when handling complex state transitions in modern web applications:

functionhandleUserAction(state,action){returnmatch([state,action]){when[{status:'idle'},{type:'FETCH_START'}]=>({status:'loading',data:state.data}),when[{status:'loading'},{type:'FETCH_SUCCESS',payload}]=>({status:'success',data:payload,error:null}),when[{status:'loading'},{type:'FETCH_ERROR',error}]=>({status:'error',data:null,error}),when[{status:'success'},{type:'REFRESH'}]=>({status:'loading',data:state.data}),when_=>state};}
Enter fullscreen modeExit fullscreen mode

The equivalent code without pattern matching is significantly more verbose:

functionhandleUserAction(state,action){// Check idle state + fetch startif(state.status==='idle'&&action.type==='FETCH_START'){return{status:'loading',data:state.data};}// Check loading state + fetch successif(state.status==='loading'&&action.type==='FETCH_SUCCESS'){return{status:'success',data:action.payload,error:null};}// Check loading state + fetch errorif(state.status==='loading'&&action.type==='FETCH_ERROR'){return{status:'error',data:null,error:action.error};}// Check success state + refreshif(state.status==='success'&&action.type==='REFRESH'){return{status:'loading',data:state.data};}// Default: return unchanged statereturnstate;}
Enter fullscreen modeExit fullscreen mode

Pattern matching also provides exhaustiveness checking - the compiler warns you if you've missed handling a possible case. This eliminates an entire class of bugs that plague traditional conditional logic.

Here's another practical example for parsing configuration formats:

functionparseConfig(config){returnmatch(config){when{version:1,settings:Object(settings)}=>parseV1Settings(settings),when{version:2,config:Object(settings)}=>parseV2Settings(settings),whenString(jsonString)=>parseConfig(JSON.parse(jsonString)),when[String(env),Object(overrides)]=>mergeConfigs(getEnvConfig(env),overrides),when_=>thrownewError(`Invalid configuration format:${JSON.stringify(config)}`)};}
Enter fullscreen modeExit fullscreen mode

How to Use It Today

Pattern matching is currently at Stage 1 in the TC39 process as of May 2025, which means it's still in the proposal phase with ongoing discussions about syntax and semantics. However, you can experiment with it today:

With Babel (Generic Setup):

# Install the pattern matching pluginnpminstall--save-dev babel-plugin-proposal-pattern-matching
Enter fullscreen modeExit fullscreen mode

Add to your.babelrc:

{"plugins":["babel-plugin-proposal-pattern-matching"]}
Enter fullscreen modeExit fullscreen mode

With Vite:

# Install dependenciesnpminstall--save-dev vite-plugin-babel @babel/core babel-plugin-proposal-pattern-matching
Enter fullscreen modeExit fullscreen mode
// vite.config.jsimport{defineConfig}from'vite';importbabelfrom'vite-plugin-babel';exportdefaultdefineConfig({plugins:[babel({babelConfig:{plugins:['babel-plugin-proposal-pattern-matching']}})]});
Enter fullscreen modeExit fullscreen mode

With Next.js:

# Install the pattern matching pluginnpminstall--save-dev babel-plugin-proposal-pattern-matching
Enter fullscreen modeExit fullscreen mode
// .babelrc{"presets":["next/babel"],"plugins":["babel-plugin-proposal-pattern-matching"]}
Enter fullscreen modeExit fullscreen mode

With tsup:

# Install dependenciesnpminstall--save-dev tsup @babel/core babel-plugin-proposal-pattern-matching
Enter fullscreen modeExit fullscreen mode
// tsup.config.tsimport{defineConfig}from'tsup';import*asbabelfrom'@babel/core';importfsfrom'fs';exportdefaultdefineConfig({entry:['src/index.ts'],format:['cjs','esm'],dts:true,esbuildPlugins:[{name:'babel',setup(build){build.onLoad({filter:/\.(jsx?|tsx?)$/},async(args)=>{constsource=awaitfs.promises.readFile(args.path,'utf8');constresult=awaitbabel.transformAsync(source,{filename:args.path,presets:[['@babel/preset-env',{targets:'defaults'}],'@babel/preset-typescript'],plugins:['babel-plugin-proposal-pattern-matching']});return{contents:result?.code||'',loader:args.path.endsWith('x')?'jsx':'js'};});}}]});
Enter fullscreen modeExit fullscreen mode

Production Alternatives:

For production code today, use the ts-pattern library:

npminstallts-pattern
Enter fullscreen modeExit fullscreen mode
import{match,P}from'ts-pattern';functionprocessMessage(message:unknown){returnmatch(message).with(P.string,text=>`Text message:${text}`).with([P.string,P.string],([sender,content])=>`Message from${sender}:${content}`).with({type:'error',content:P.string,code:P.number},({content,code})=>`Error:${content} (Code:${code})`).otherwise(()=>'Unknown message format');}
Enter fullscreen modeExit fullscreen mode

Temporal API Solves Date Handling Problems

JavaScript's built-inDate object has long been a source of frustration for developers. It's mutable (dates can be accidentally modified), has confusing month indexing (January is 0!), and timezone handling is problematic. These issues have led to the widespread use of libraries like Moment.js, date-fns, and Luxon.

The Temporal API provides a complete solution to these problems by reimagining date and time handling in JavaScript.

Here's a practical example of scheduling a meeting across timezones:

// Current approach with Date (likely to have bugs)functionscheduleMeeting(startDate,durationInMinutes,timeZone){conststart=newDate(startDate);constend=newDate(start.getTime()+durationInMinutes*60000);return{start:start.toISOString(),end:end.toISOString(),timeZone:timeZone// Not actually used in calculations};}// With Temporal APIfunctionscheduleMeeting(startDateTime,durationInMinutes,timeZone){conststart=Temporal.ZonedDateTime.from(startDateTime);constend=start.add({minutes:durationInMinutes});return{start:start.toString(),end:end.toString(),timeZone:start.timeZoneId// Properly tracked};}
Enter fullscreen modeExit fullscreen mode

For international flight booking systems, calculating flight durations across timezones becomes straightforward:

// Current approach with Date (error-prone)functioncalculateFlightDuration(departure,arrival,departureTimeZone,arrivalTimeZone){// Convert to milliseconds and calculate differenceconstdepartureTime=newDate(departure);constarrivalTime=newDate(arrival);// This doesn't account for timezone differences correctlyconstdurationMs=arrivalTime-departureTime;return{hours:Math.floor(durationMs/(1000*60*60)),minutes:Math.floor((durationMs%(1000*60*60))/(1000*60)),// No easy way to get arrival in departure's timezone};}// With Temporal APIfunctioncalculateFlightDuration(departure,arrival){constdepartureTime=Temporal.ZonedDateTime.from(departure);constarrivalTime=Temporal.ZonedDateTime.from(arrival);// Accurate duration calculation across time zonesconstduration=departureTime.until(arrivalTime);return{hours:duration.hours,minutes:duration.minutes,inLocalTime:arrivalTime.toLocaleString(),inDepartureTime:arrivalTime.withTimeZone(departureTime.timeZoneId).toLocaleString()};}
Enter fullscreen modeExit fullscreen mode

Here's another example showing how Temporal API handles recurring events, which are notoriously difficult with the current Date object:

// Current approach with Date (complex and error-prone)functiongetNextMeetingDates(startDate,count){constdates=[];constcurrent=newDate(startDate);for(leti=0;i<count;i++){dates.push(newDate(current));// Add 2 weeks - error-prone due to month boundaries, DST changes, etc.current.setDate(current.getDate()+14);}returndates;}// With Temporal APIfunctiongetNextMeetingDates(startDate,count){conststart=Temporal.PlainDate.from(startDate);constdates=[];for(leti=0;i<count;i++){constnextDate=start.add({days:i*14});dates.push(nextDate);}returndates;}
Enter fullscreen modeExit fullscreen mode

The Temporal API provides several key advantages:

  1. Immutability: All Temporal objects are immutable, preventing accidental modifications
  2. Separate types for different use cases: PlainDate, PlainTime, ZonedDateTime, etc.
  3. Intuitive methods: Clear, chainable methods for date arithmetic
  4. Proper timezone handling: Built-in support for timezones and daylight saving time
  5. Consistent behavior: Works the same way across all browsers

How to Use It Today

The Temporal API is at Stage 3 in the TC39 process as of May 2025, which means it's nearing completion and implementations are being developed. Here's how to use it today:

With the Official Polyfill:

npminstall @js-temporal/polyfill
Enter fullscreen modeExit fullscreen mode
// Import in your codeimport{Temporal}from'@js-temporal/polyfill';// Get the current date and timeconstnow=Temporal.Now.plainDateTimeISO();console.log(`Current time:${now.toString()}`);
Enter fullscreen modeExit fullscreen mode

With Vite:

# Install the Temporal API polyfillnpminstall @js-temporal/polyfill
Enter fullscreen modeExit fullscreen mode
// main.js or any entry fileimport{Temporal}from'@js-temporal/polyfill';// Now you can use Temporal API in your codeconstnow=Temporal.Now.plainDateTimeISO();console.log(`Current time:${now.toString()}`);
Enter fullscreen modeExit fullscreen mode

With Next.js:

# Install the Temporal API polyfillnpminstall @js-temporal/polyfill
Enter fullscreen modeExit fullscreen mode
// pages/_app.jsimport{Temporal}from'@js-temporal/polyfill';// Make Temporal available globally if neededif(typeofwindow!=='undefined'){window.Temporal=Temporal;}functionMyApp({Component,pageProps}){return<Component{...pageProps}/>;}exportdefaultMyApp;
Enter fullscreen modeExit fullscreen mode

With tsup:

# Install the Temporal API polyfillnpminstall @js-temporal/polyfill
Enter fullscreen modeExit fullscreen mode
// src/index.tsimport{Temporal}from'@js-temporal/polyfill';// Export it if you want to make it available to consumers of your packageexport{Temporal};// Your other code here
Enter fullscreen modeExit fullscreen mode

Browser Support:

  • Chrome/Edge: Available behind the "Experimental JavaScript" flag
  • Firefox: Available in Firefox 139 and later
  • Safari: Not yet implemented

To check if Temporal is natively supported:

if(typeofTemporal!=='undefined'){console.log('Temporal API is natively supported');}else{console.log('Using polyfill');// Import polyfill}
Enter fullscreen modeExit fullscreen mode

Resource Management Eliminates Memory Leaks

JavaScript has long lacked deterministic resource cleanup. When working with files, database connections, or hardware access, developers must manually ensure resources are properly released - even in error cases.

This limitation leads to memory leaks and resource exhaustion bugs. The typical pattern involves try/finally blocks that quickly become unwieldy:

asyncfunctionprocessFile(path){letfile=null;try{file=awaitfs.promises.open(path,'r');constcontent=awaitfile.readFile({encoding:'utf8'});returnprocessContent(content);}finally{if(file){awaitfile.close();}}}
Enter fullscreen modeExit fullscreen mode

The newusing andawait using statements provide deterministic resource management in #"http://www.w3.org/2000/svg" width="20px" height="20px" viewbox="0 0 24 24">Enter fullscreen modeExit fullscreen mode

Let's look at a more complex example with multiple resources that must be managed in a specific order:

// Current approach: Nested try/finally blocksasyncfunctionprocessData(dbConfig,filePath){letdb=null;letfile=null;try{db=awaitDatabase.connect(dbConfig);try{file=awaitfs.promises.open(filePath,'r');constdata=awaitfile.readFile({encoding:'utf8'});constprocessed=processRawData(data);returndb.store(processed);}finally{if(file){awaitfile.close();}}}finally{if(db){awaitdb.disconnect();}}}// With resource management: Clean and safeasyncfunctionprocessData(dbConfig,filePath){awaitusingdb=awaitDatabase.connect(dbConfig);awaitusingfile=awaitfs.promises.open(filePath,'r');constdata=awaitfile.readFile({encoding:'utf8'});constprocessed=processRawData(data);returndb.store(processed);// Resources are automatically cleaned up in reverse order:// 1. file is closed// 2. db is disconnected}
Enter fullscreen modeExit fullscreen mode

For database-heavy applications, this feature transforms connection pool management:

classDatabaseConnection{constructor(config){this.config=config;this.connection=null;}asyncconnect(){this.connection=awaitcreateConnection(this.config);returnthis;}asyncquery(sql,params){returnthis.connection.query(sql,params);}async[Symbol.asyncDispose](){if(this.connection){awaitthis.connection.close();this.connection=null;}}}// Using the connection with automatic cleanupasyncfunctiongetUserData(userId){awaitusingdb=awaitnewDatabaseConnection(config).connect();returndb.query('SELECT * FROM users WHERE id = ?',[userId]);// Connection is automatically closed when the function exits}
Enter fullscreen modeExit fullscreen mode

Here's another example showing how to manage hardware resources like WebUSB devices:

// Current approach: Manual cleanup requiredasyncfunctionreadFromUSBDevice(deviceFilter){letdevice=null;try{constdevices=awaitnavigator.usb.getDevices();device=devices.find(d=>d.productId===deviceFilter.productId);if(!device){device=awaitnavigator.usb.requestDevice({filters:[deviceFilter]});}awaitdevice.open();awaitdevice.selectConfiguration(1);awaitdevice.claimInterface(0);constresult=awaitdevice.transferIn(1,64);returnnewTextDecoder().decode(result.data);}finally{if(device){try{awaitdevice.close();}catch(e){console.error("Error closing device:",e);}}}}// With resource management: Automatic cleanupclassUSBDeviceResource{constructor(device){this.device=device;}staticasynccreate(deviceFilter){constdevices=awaitnavigator.usb.getDevices();letdevice=devices.find(d=>d.productId===deviceFilter.productId);if(!device){device=awaitnavigator.usb.requestDevice({filters:[deviceFilter]});}awaitdevice.open();awaitdevice.selectConfiguration(1);awaitdevice.claimInterface(0);returnnewUSBDeviceResource(device);}asyncread(){constresult=awaitthis.device.transferIn(1,64);returnnewTextDecoder().decode(result.data);}async[Symbol.asyncDispose](){try{awaitthis.device.close();}catch(e){console.error("Error closing device:",e);}}}asyncfunctionreadFromUSBDevice(deviceFilter){awaitusingdevice=awaitUSBDeviceResource.create(deviceFilter);returndevice.read();// Device is automatically closed when the function exits}
Enter fullscreen modeExit fullscreen mode

How to Use It Today

The Explicit Resource Management proposal (using/await using) is at Stage 3 in the TC39 process as of May 2025, which means it's nearing standardization. Here's how to use it today:

With Babel (Generic Setup):

# Install the resource management pluginnpminstall--save-dev @babel/plugin-proposal-explicit-resource-management
Enter fullscreen modeExit fullscreen mode

Add to your.babelrc:

{"plugins":["@babel/plugin-proposal-explicit-resource-management"]}
Enter fullscreen modeExit fullscreen mode

With Vite:

# Install dependenciesnpminstall--save-dev vite-plugin-babel @babel/core @babel/plugin-proposal-explicit-resource-management
Enter fullscreen modeExit fullscreen mode
// vite.config.jsimport{defineConfig}from'vite';importbabelfrom'vite-plugin-babel';exportdefaultdefineConfig({plugins:[babel({babelConfig:{plugins:['@babel/plugin-proposal-explicit-resource-management']}})]});
Enter fullscreen modeExit fullscreen mode

With Next.js:

# Install the resource management pluginnpminstall--save-dev @babel/plugin-proposal-explicit-resource-management
Enter fullscreen modeExit fullscreen mode
// .babelrc{"presets":["next/babel"],"plugins":["@babel/plugin-proposal-explicit-resource-management"]}
Enter fullscreen modeExit fullscreen mode

With tsup:

# Install dependenciesnpminstall--save-dev tsup @babel/core @babel/plugin-proposal-explicit-resource-management
Enter fullscreen modeExit fullscreen mode
// tsup.config.tsimport{defineConfig}from'tsup';import*asbabelfrom'@babel/core';importfsfrom'fs';exportdefaultdefineConfig({entry:['src/index.ts'],format:['cjs','esm'],dts:true,esbuildPlugins:[{name:'babel',setup(build){build.onLoad({filter:/\.(jsx?|tsx?)$/},async(args)=>{constsource=awaitfs.promises.readFile(args.path,'utf8');constresult=awaitbabel.transformAsync(source,{filename:args.path,presets:[['@babel/preset-env',{targets:'defaults'}],'@babel/preset-typescript'],plugins:['@babel/plugin-proposal-explicit-resource-management']});return{contents:result?.code||'',loader:args.path.endsWith('x')?'jsx':'js'};});}}]});
Enter fullscreen modeExit fullscreen mode

Making Objects Disposable:

To make your objects work withusing, implement theSymbol.dispose method:

classMyResource{[Symbol.dispose](){// Cleanup code here}}
Enter fullscreen modeExit fullscreen mode

For async resources, implementSymbol.asyncDispose:

classMyAsyncResource{async[Symbol.asyncDispose](){// Async cleanup code here}}
Enter fullscreen modeExit fullscreen mode

Polyfill for Symbols:

// polyfill.jsif(!Symbol.dispose){Symbol.dispose=Symbol("Symbol.dispose");}if(!Symbol.asyncDispose){Symbol.asyncDispose=Symbol("Symbol.asyncDispose");}
Enter fullscreen modeExit fullscreen mode

Decorators Add Functionality Without Changing Core Logic

JavaScript decorators provide a clean way to modify classes and methods with additional functionality. If you've used TypeScript or frameworks like Angular, you're already familiar with the concept. Now decorators are coming to vanilla JavaScript.

Decorators solve a fundamental problem: how to add cross-cutting concerns like logging, validation, or performance monitoring without cluttering your business logic.

Here's a practical example:

classUserController{@authenticate@rateLimit(100)@validate(userSchema)@logAccessasyncupdateUserProfile(userId,profileData){// The method is automatically wrapped with:// 1. Authentication check// 2. Rate limiting (100 requests per hour)// 3. Input validation against userSchema// 4. Access loggingconstuser=awaitthis.userRepository.findById(userId);Object.assign(user,profileData);returnthis.userRepository.save(user);}}
Enter fullscreen modeExit fullscreen mode

Without decorators, you'd need to manually wrap each method, resulting in code that's harder to read and maintain:

classUserController{asyncupdateUserProfile(userId,profileData){// Authentication checkif(!isAuthenticated()){thrownewError('Unauthorized');}// Rate limitingif(isRateLimited(this.constructor.name,'updateUserProfile',100)){thrownewError('Rate limit exceeded');}// Input validationconstvalidationResult=validateWithSchema(userSchema,profileData);if(!validationResult.valid){thrownewError(`Invalid data:${validationResult.errors.join(',')}`);}// LogginglogAccess(this.constructor.name,'updateUserProfile',userId);// Actual business logicconstuser=awaitthis.userRepository.findById(userId);Object.assign(user,profileData);returnthis.userRepository.save(user);}}
Enter fullscreen modeExit fullscreen mode

Here's a real-world example of using decorators for performance monitoring:

classDataProcessor{@measure@cacheprocessLargeDataset(data){// Complex and time-consuming operationreturndata.map(item=>/* complex transformation */).filter(item=>/* complex filtering */).reduce((acc,item)=>/* complex aggregation */);}}// Performance measurement decoratorfunctionmeasure(target,name,descriptor){constoriginal=descriptor.value;descriptor.value=function(...args){conststart=performance.now();constresult=original.apply(this,args);constend=performance.now();console.log(`${name} took${end-start}ms to execute`);returnresult;};returndescriptor;}// Caching decoratorfunctioncache(target,name,descriptor){constoriginal=descriptor.value;constcacheStore=newMap();descriptor.value=function(...args){constkey=JSON.stringify(args);if(cacheStore.has(key)){console.log(`Cache hit for${name}`);returncacheStore.get(key);}console.log(`Cache miss for${name}`);constresult=original.apply(this,args);cacheStore.set(key,result);returnresult;};returndescriptor;}
Enter fullscreen modeExit fullscreen mode

Decorators are particularly valuable for API development. Here's an example of using decorators to implement a RESTful API with proper error handling:

classProductAPI{@route('GET','/products')@paginate@handleErrorsasyncgetAllProducts(req){returnthis.productRepository.findAll();}@route('GET','/products/:id')@handleErrorsasyncgetProductById(req){constproduct=awaitthis.productRepository.findById(req.params.id);if(!product){thrownewNotFoundError(`Product with ID${req.params.id} not found`);}returnproduct;}@route('POST','/products')@validate(productSchema)@handleErrorsasynccreateProduct(req){returnthis.productRepository.create(req.body);}}// Route decoratorfunctionroute(method,path){returnfunction(target,name,descriptor){if(!target.constructor._routes){target.constructor._routes=[];}target.constructor._routes.push({method,path,handler:descriptor.value,name});returndescriptor;};}// Error handling decoratorfunctionhandleErrors(target,name,descriptor){constoriginal=descriptor.value;descriptor.value=asyncfunction(req,res){try{constresult=awaitoriginal.call(this,req);returnres.json(result);}catch(error){if(errorinstanceofNotFoundError){returnres.status(404).json({error:error.message});}if(errorinstanceofValidationError){returnres.status(400).json({error:error.message});}console.error(`Error in${name}:`,error);returnres.status(500).json({error:'Internal server error'});}};returndescriptor;}
Enter fullscreen modeExit fullscreen mode

How to Use Decorators Today

Decorators are at Stage 3 in the TC39 process as of May 2025, with implementations in progress in major browsers. Here's how to use them today:

With Babel (Generic Setup):

# Install the decorators pluginnpminstall--save-dev @babel/plugin-proposal-decorators
Enter fullscreen modeExit fullscreen mode

Add to your.babelrc:

{"plugins":[["@babel/plugin-proposal-decorators",{"version":"2023-05"}]]}
Enter fullscreen modeExit fullscreen mode

With Vite:

# Install dependenciesnpminstall--save-dev vite-plugin-babel @babel/core @babel/plugin-proposal-decorators
Enter fullscreen modeExit fullscreen mode
// vite.config.jsimport{defineConfig}from'vite';importbabelfrom'vite-plugin-babel';exportdefaultdefineConfig({plugins:[babel({babelConfig:{plugins:[['@babel/plugin-proposal-decorators',{version:'2023-05'}]]}})]});
Enter fullscreen modeExit fullscreen mode

With Next.js:

# Install the decorators pluginnpminstall--save-dev @babel/plugin-proposal-decorators
Enter fullscreen modeExit fullscreen mode
// .babelrc{"presets":["next/babel"],"plugins":[["@babel/plugin-proposal-decorators",{"version":"2023-05"}]]}
Enter fullscreen modeExit fullscreen mode

With TypeScript:

Enable decorators intsconfig.json:

{"compilerOptions":{"target":"ES2022","experimentalDecorators":true}}
Enter fullscreen modeExit fullscreen mode

With tsup:

# Install dependenciesnpminstall--save-dev tsup @babel/core @babel/plugin-proposal-decorators
Enter fullscreen modeExit fullscreen mode
// tsup.config.tsimport{defineConfig}from'tsup';import*asbabelfrom'@babel/core';importfsfrom'fs';exportdefaultdefineConfig({entry:['src/index.ts'],format:['cjs','esm'],dts:true,esbuildPlugins:[{name:'babel',setup(build){build.onLoad({filter:/\.(jsx?|tsx?)$/},async(args)=>{constsource=awaitfs.promises.readFile(args.path,'utf8');constresult=awaitbabel.transformAsync(source,{filename:args.path,presets:[['@babel/preset-env',{targets:'defaults'}],'@babel/preset-typescript'],plugins:[['@babel/plugin-proposal-decorators',{version:'2023-05'}]]});return{contents:result?.code||'',loader:args.path.endsWith('x')?'jsx':'js'};});}}]});
Enter fullscreen modeExit fullscreen mode

Important Note:

The decorator proposal has gone through several iterations. Make sure you're using the latest syntax (2023-05 version) as older versions are incompatible. The current specification defines three capabilities for decorators:

  • They canreplace the decorated value with a matching value
  • They can provideaccess to the decorated value
  • They caninitialize the decorated value

Testing Your Configuration

After setting up your configuration, create a simple test file to verify that the features are working:

// test-features.js// Test pipeline operatorconstdouble=x=>x*2;constadd=x=>x+1;constsquare=x=>x*x;constresult=5|>double|>add|>square;console.log("Pipeline result:",result);// Should be 121// Test pattern matchingfunctionprocessValue(value){returnmatch(value){whenString(s)=>`String:${s}`,whenNumber(n)=>`Number:${n}`,when{type,data}=>`Object with type${type}`,when_=>'Unknown'};}console.log("Pattern matching:",processValue("test"),processValue(42),processValue({type:"user",data:{}}));// Test Temporal APIimport{Temporal}from'@js-temporal/polyfill';constnow=Temporal.Now.plainDateTimeISO();console.log("Current time:",now.toString());// Test resource managementclassResource{constructor(name){this.name=name;console.log(`Resource${name} created`);}[Symbol.dispose](){console.log(`Resource${this.name} disposed`);}}{usingresource=newResource("test");console.log("Using resource");}// Resource should be disposed here// Test decoratorsfunctionlog(target,name,descriptor){constoriginal=descriptor.value;descriptor.value=function(...args){console.log(`Calling${name} with${JSON.stringify(args)}`);returnoriginal.apply(this,args);};returndescriptor;}classCalculator{@logadd(a,b){returna+b;}}constcalc=newCalculator();console.log("Decorator result:",calc.add(2,3));
Enter fullscreen modeExit fullscreen mode

Conclusion

These upcoming JavaScript features aren't just syntactic sugar - they fundamentally change how we write code. AtLingo.dev, we've already seen how they can transform complex, error-prone code into clean, maintainable solutions.

The best part is you don't have to wait. With the right tools, you can start using these features today. Each one solves real problems that developers face daily, from managing resources to handling dates correctly.

What upcoming JavaScript feature are you most excited about? Have you found other ways to solve these problems in your codebase?

Share your thoughts!


Useful links:

Top comments(26)

Subscribe
pic
Create template

Templates let you quickly answer FAQs or store snippets for re-use.

Dismiss
CollapseExpand
 
steven_dix_755c2b861b6d92 profile image
Steven Dix
  • Joined

I don't think i agree with the pipeline operator being as useful however, or maybe i just have an issue with the example as it seems very apples to oranges. It's displayed that it's an alternative to a set of nested functions but tbh if i was creating functions to be chained ( like how Arrays have ). I would just do this:

export class User {  private user: User;  constructor(user: User) {    this.user = user;  }  static enrichUserProfile() {    /* do something and amend this.user */    return this;  }  static normalizeData() {    /* do something and amend this.user */    return this;  }  static validateUser() {    /* do something and amend this.user */    return this;  }}
Enter fullscreen modeExit fullscreen mode

and i would then argue that just havingUser(ourUser).enrichUserProfile().normalizeData().validateUser(). saveToDatabase()

Is nicer than a|> between each method, and needing to polyfill for earlier versions.

CollapseExpand
 
framemuse profile image
Valery Zinchenko
Self-taught developer. Contribute to open source. Question everything, why everything's so slow...
  • Email
  • Location
    Montenegro
  • Education
    Czeck/English/Japanese language school, Musical College and YouTube
  • Pronouns
    🌸
  • Work
    Web-engineer at Pinely International
  • Joined

In case of a class build composition, you can't use other methods in the build chain, but with pipeline - you can.

CollapseExpand
 
vrcprl profile image
Veronica Prilutskaya 🌱
building Lingo.dev.
  • Location
    San Francisco, California
  • Work
    Lingo.dev
  • Joined

Another great article, Max! Thanks for sharing!

Love the new pipeline operator(|>) - the code looks so clean!

CollapseExpand
 
maxprilutskiy profile image
Max Prilutskiy
Hacking on open source, with JavaScript and LLMs. Thoughts on software development, and unusual tech stuff.
  • Location
    San Francisco, California
  • Work
    Author of Lingo.dev
  • Joined

we're moving parts of the codebase to use it now. it's great!

CollapseExpand
 
ryanguitar profile image
Ryan Els
Frontend Developer
  • Email
  • Location
    Cape Town
  • Work
    I have made my first professional website. at Security sector
  • Joined

Cool 😎 👌

CollapseExpand
 
danibrear profile image
Dani Brear
  • Location
    Richmond, Virginia
  • Joined

Been writing elixir for 6 months and these are all things I was thinking “wow, this would be cool if I could do in JS”. So cool to see the proposals coming down the pipe (pun not intended but appreciated)

CollapseExpand
 
dantman profile image
Daniel Friesen
  • Joined

I live that Temporal is finally becoming a thing. Though it doesn't feel right to call the article "upcoming" when it's talking about some proposals that are in stage 2 and 1 and were in that stage last time I checked ages ago. I'd really like to see some progress to be excited about, like Temporal, them an article would be exciting.

CollapseExpand
 
dotallio profile image
Dotallio
  • Joined

This was packed with so many practical examples, love it! For me, the pipeline operator is a total game changer for making workflows readable, but pattern matching is right up there too. Have you run into any gotchas using the Babel plugin in bigger projects?

CollapseExpand
 
jswhisperer profile image
Greg, The JavaScript Whisperer
Specialist in performance oriented javascript architecture for web, mobile, client and server side. Passionate about realtime web.

Really nice article! I learnt some new tricks.
to me I think the gold star is Temporal API

I'm not sold on the pipeline operator, I'm on the fence about it, I kinda like the indentation of nested functions to visualise whats going on; and I think often it might be a code smell hint to refactor to async await promises if your code becomes nested like that. 🤷‍♂️

CollapseExpand
 
thetos_ profile image
Thetos
I love video games an aspire to create some of my own.I write in many (programming) languages.
  • Location
    France
  • Education
    IUT INFO Annecy - EPITA Paris
  • Pronouns
    he/him
  • Work
    Full-Stack developer @ CEO-Vision
  • Joined

I'm pretty sure the pipeline operator proposal has moved to a slightly different syntax with a placeholder symbol (like Hack) instead of these needing unary functions (like F#), so that it could play better with async/await among other things

CollapseExpand
 
nevodavid profile image
Nevo David
Founder of Postiz, an open-source social media scheduling tool.Running Gitroom, the best place to learn how to grow open-source tools.
  • Education
    Didn't finish high school :(
  • Pronouns
    Nev/Nevo
  • Work
    OSS Chief @ Gitroom
  • Joined

Pretty cool seeing JavaScript finally fix a lot of old headaches - kinda pumped to mess with some of this now tbh.

CollapseExpand
 
jkalandarov profile image
Jasurbek Kalandarov
QA Automation Engineer
  • Email
  • Location
    Uzbekistan
  • Education
    MBA in Finance
  • Work
    Epam Systems
  • Joined

When all languages try to transition to js syntax, js wants to adopt PHP syntax??

CollapseExpand
 
alkawero_ef342b4b2a1d5995 profile image
alkawero
used to code since 2010
  • Joined

do you mean pipeline operator? it is not from PHP, F# was the first use it

CollapseExpand
 
narehate78 profile image
Nare Hate
  • Joined

I really like the pipeline operator!
Regarding Date, I though Moment.js was deprecated and not maintained anymore, am I wrong since you're talking about it? Luxon is my way to go now.

CollapseExpand
 
maxprilutskiy profile image
Max Prilutskiy
Hacking on open source, with JavaScript and LLMs. Thoughts on software development, and unusual tech stuff.
  • Location
    San Francisco, California
  • Work
    Author of Lingo.dev
  • Joined

Luxon is nice! Moment.js is still in lots of codebases though.

View full discussion (26 comments)

Are you sure you want to hide this comment? It will become hidden in your post, but will still be visible via the comment'spermalink.

For further actions, you may consider blocking this person and/orreporting abuse

Max Prilutskiy
Hacking on open source, with JavaScript and LLMs. Thoughts on software development, and unusual tech stuff.
  • Location
    San Francisco, California
  • Work
    Author of Lingo.dev
  • Joined

More fromMax Prilutskiy

Introducing Lingo.dev Compiler: Localize a React app without rewriting its code
#react#webdev#javascript
Shocking Things You Can Do in JavaScript
#webdev#javascript
HTML5 Elements You Didn't Know You Need
#webdev#javascript#html#css
DEV Community

We're a place where coders share, stay up-to-date and grow their careers.

Log in Create account

[8]ページ先頭

©2009-2025 Movatter.jp