JavaScript-ன் async/await தொடரியல் (syntax) நம்மை callback hell-லிருந்து காப்பாற்ற வேண்டியிருந்தது. ஆனால், அதற்குப் பதிலாக, அது ஒரு அமைதியான மற்றும் நுட்பமான சிக்கலை அறிமுகப்படுத்தியது: பார்ப்பதற்குச் சரியாகத் தோன்றும் ஆனால் கணிக்க முடியாத வகையில் செயல்படும் குறியீடு (code). ஒரு function-ன் உள்ளே await இருப்பதைப் பார்த்து, ஒவ்வொன்றும் வரி வரியாகத் தற்காலிகமாக நிற்கும் என்று நீங்கள் நினைக்கலாம். ஆனால் பெரும்பாலும் அப்படி நடப்பதில்லை. Loops வேகமாகத் தாண்டிச் சென்றுவிடும். ஒரே ஒரு request தோல்வியடைந்தாலும், முழு தொகுதியும் (batch) செயலிழந்துவிடும். காரணமே இல்லாமல் entry files-களில் தேவையில்லாத async wrappers உருவாகும். நீங்கள் இத்தகைய சிக்கல்களைச் சந்தித்திருந்தால், இந்த மூன்று முறைகள் (patterns) அவற்றைச் சரிசெய்ய உதவும்.
forEach-க்குள் await பயன்படுத்துவதை நிறுத்துங்கள்
பார்க்கதற்குப் பாதிப்பில்லாதது போல் தோன்றும் ஒரு பொதுவான தவறு இதோ:
const urls = ['/api/user', '/api/posts', '/api/comments'];
urls.forEach(async (url) => {
const res = await fetch(url);
const data = await res.json();
console.log(data);
});
console.log('All done!');
இதை இயக்கினால், ஒரு response கூட வருவதற்கு முன்பே 'All done!' என்று அச்சிடப்படும். ஏன்? forEach ஒவ்வொரு உறுப்பிற்கும் (element) callback-ஐ உடனடியாகச் செயல்படுத்தும். ஒவ்வொரு iteration-க்குள்ளும் இருக்கும் promise-க்காக அது காத்திருக்காது. async keyword ஒவ்வொரு callback-ஐயும் ஒரு promise-ஆக மாற்றுகிறது, ஆனால் forEach அதை உடனடியாகப் புறக்கணித்துவிடும். உங்கள் loop மைக்ரோ விநாடிகளில் முடிந்துவிடும்; ஆனால் network requests தனியாகத் தொடர்ந்து கொண்டிருக்கும். பிழைகளை (errors) வரிசையாகக் கையாள வேண்டுமானால், அல்லது அடுத்த request தொடங்குவதற்கு முன் முந்தையது முடிந்துவிடும் என்பதை உறுதிப்படுத்த வேண்டுமானால், இந்த முறை அந்த இரண்டு உத்தரவாதங்களையும் அமைதியாக உடைத்துவிடும்.
அதற்குப் பதிலாக for...of loop-ஐப் பயன்படுத்துங்கள்:
const urls = ['/api/user', '/api/posts', '/api/comments'];
for (const url of urls) {
const res = await fetch(url);
const data = await res.json();
console.log(data);
}
console.log('All done!');
இப்போது loop ஒவ்வொரு await-இடத்திலும் உண்மையாகவே நிற்கும். இரண்டாவது request முதல் request முடிவடைவதற்காகக் காத்திருக்கும். அனைத்தும் முடிந்த பின்னரே 'All done!' என்று அச்சிடப்படும்.
வரிசை முறை (sequence) முக்கியம் என்று கருதப்படும் இடங்களில் for...of-ஐப் பயன்படுத்துங்கள்—உதாரணமாக, rate limits-ஐக் கடைப்பிடிக்க கோப்புகளை ஒவ்வொன்றாகப் பதிவேற்றுதல் (uploading), குறிப்பிட்ட வரிசையில் database rows-களை எழுதுதல், அல்லது முந்தைய response-லிருந்து தரவு தேவைப்படும் API calls-களை இணைத்தல். நீங்கள் உண்மையில் இணையாக (parallel execution) இயக்க விரும்பினால், forEach-ஐக் குழப்பிக் கொள்ளாதீர்கள். உங்கள் நோக்கம் அடுத்த டெவலப்பருக்குத் தெளிவாகத் தெரியும்படி, வெளிப்படையாக Promise.all-ஐப் பயன்படுத்துங்கள். ஆனால், synchronous behavior கிடைக்கும் என்று எதிர்பார்த்து await மற்றும் forEach-ஐ ஒருபோதும் கலக்காதீர்கள். அது நடக்காது.
பூஜ்ஜியம் (Zero) தீர்வாக அமைய முடியாத போது Promise.allSettled-ஐப் பயன்படுத்துங்கள்
Promise.all என்பது அர்த்தமுள்ளதாகச் செயல்படுகிறது. அதற்கு ஒரு array of promises-ஐக் கொடுத்தால், அது ஒரு array of results-ஐத் திருப்பித் தரும். ஆனால் இதில் உள்ள சிக்கல் என்னவென்றால், ஏதேனும் ஒரு promise தோல்வியடைந்தால் (reject), முழு தொகுதியும் உடனடியாகத் தோல்வியடைந்துவிடும். மற்ற அனைத்து நிலுவையில் உள்ள (pending) promise-களும் அவற்றின் வேலையைத் தொடரும், ஆனால் அவற்றின் முடிவுகளைப் பெற உங்களால் முடியாது. Production சூழலில், இந்த "எல்லாம் அல்லது ஒன்றுமில்லை" (all-or-nothing) என்ற நடத்தை பெரும் பாதிப்பை ஏற்படுத்தும்.
உங்கள் application ஒரு dashboard-ன் widgets-களை நான்கு தனித்தனி சேவைகளிலிருந்து பெறுகிறது என்று கற்பனை செய்து கொள்ளுங்கள்: traffic analytics, revenue data, user feedback, மற்றும் server health. இதில் revenue API ஒரு சிறிய timeout-ஐச் சந்திக்கிறது. Promise.all-ன் கீழ், உங்கள் முழு dashboard-ம் ஒரு error-ஐக் காட்டும். மற்ற மூன்று சரியான responses-களும் காணாமல் போய்விடும். தரவில் கால் பகுதி மட்டும் தவறாகச் செயல்பட்டதால், பயனர் ஒரு spinner-ஐப் பார்த்துவிட்டு, பிறகு failure screen-ஐப் பார்ப்பார்.
Promise.allSettled உங்களுக்கு ஒரு சிறந்த தீர்வைத் தருகிறது. ஒவ்வொரு promise-ம் எப்படி முடிவடைந்தாலும் (வெற்றி அல்லது தோல்வி), அது அனைத்தும் முடியும் வரை காத்திருக்கும். அதன் முடிவாகக் கிடைக்கும் value, ஒவ்வொரு முடிவையும் விவரிக்கும் objects கொண்ட ஒரு array ஆகும்:
const requests = [
fetch('/api/traffic'),
fetch('/api/revenue'),
fetch('/api/feedback'),
fetch('/api/health')
];
const results = await Promise.allSettled(requests);
results.forEach((result, index) => {
if (result.status === 'fulfilled') {
renderWidget(index, result.value);
} else {
renderError(index, result.reason);
}
});
எந்த ஒரு response-ம் வீணாகாது. உங்களால் முடிந்தவற்றைக் காண்பித்துவிட்டு, தோல்வியைக் தனிமைப்படுத்தலாம். தொடர்பில்லாத செயல்பாடுகளைக் கையாளும் போது (unrelated operations)—உதாரணமாக, bulk notifications, third-party webhook dispatches, அல்லது பல CSV streams-லிருந்து தரவுகளை இறக்குமதி செய்தல்—இந்த முறை மிகவும் முக்கியமானது. உங்களுக்குத் தொடர்ச்சியான error tracking தேவைப்படும், ஆனால் உங்கள் application நிலைதடுமாறாமல் இயங்கும்.
ஒரு நடைமுறை குறிப்பு: allSettled முழுத் தொகுதியையும் திருப்பித் தரும், எனவே முடிவுகளைச் சரிபார்த்து, உங்கள் feature-க்கு "பகுதி வெற்றி" (partial success) என்பது எதைக் குறிக்கிறது என்பதை நீங்கள் தீர்மானிக்க வேண்டும். திரும்பப் பெறப்பட்ட array-யை முழுமையாகச் சரியான தரவு என்று எடுத்துக்கொள்ளாதீர்கள். உங்கள் state layer-க்குள் எதையும் சேர்ப்பதற்கு முன், அந்த status fields-களைச் சரிபார்க்கவும்.
Top-Level await-ஐ அறிவித்து Wrapper IIFE-யைத் தவிர்க்கவும்
பல ஆண்டுகளாக, ஒரு file-ன் root-ல் ஏதேனும் ஒன்றிற்காக காத்திருக்க (await) வேண்டுமென்றால், அதை ஒரு immediately invoked async function-க்குள் சுற்றியே (wrap) எழுத வேண்டும்:
(async () => {
const config = await loadConfig();
startServer(config);
})();
இது வேலை செய்யும், ஆனால் இது தேவையற்ற குப்பையாகும் (noise). ES modules-ல் இயல்பாகவே உள்ள top-level await, இந்தத் தேவையற்ற boilerplate குறியீடுகளைத் தவிர்க்க அனுமதிக்கிறது:
const config = await loadConfig();
startServer(config);
உங்கள் application-ன் entry point-ல் அல்லது initialization மற்ற அனைத்தையும் விட முன்னதாக முடிய வேண்டிய configuration modules-களில் இதைப் பயன்படுத்தவும். Environment files-களை ஏற்றுதல், database connection pool-ஐ உருவாக்குதல் அல்லது remote feature flags-களைப் பெறுதல் போன்றவற்றுக்கு இது மிகவும் பொருத்தமானது. Top-level await ஆனது module graph-ன் செயல்பாட்டைத் தடுப்பதால்—அதாவது, இந்த file-ஐ import செய்யும் மற்ற files உங்கள் promise resolve ஆகும் வரை காத்திருக்கும்—உங்களுக்கு ஒரு உறுதியான நிலை (guaranteed state) கிடைக்கும். உங்கள் codebase-ன் மற்ற பகுதிகள் db-ஐ import செய்யும் போது, இணைப்பு ஏற்கனவே செயல்பாட்டில் இருப்பதை உறுதி செய்து கொள்ளலாம்.
There are two catches. First, your runtime or bundler must support ES modules. In Node.js, that means either using .mjs extensions or setting "type": "module" in your package.json. Second, because waiting at the module level delays every importer, keep the awaited work focused. Heavy sequential fetches at the top of a frequently imported utility file will slow down your entire application cold start. Reserve top-level await for true bootstrap tasks that other modules genuinely depend upon.
What Actually Changes When You Adopt These Patterns
Predictability is the first payoff. When you read a for...of loop, you know exactly when the block underneath finishes. There are no ghost promises racing behind the scenes, no foreach callbacks detaching from your error handlers. Your control flow matches the shape of the code on the screen.
Resilience comes next. Promise.allSettled forces you to think about partial failure instead of hoping every external system stays perfect. Production software is not binary. Some endpoints will flake. Some file reads will hit permission errors. Designing for the reality of scattered failures keeps your application upright without sweeping legitimate data under the rug.
Clarity ties it together. for...of reads like plain English progression. allSettled states its intent in its name. Top-level await removes cryptic IIFE wrappers so your entry files start with business logic instead of syntactic acrobatics. The next engineer who touches the file—whether that is you in six months or a teammate on a deadline—will thank you.
A Real Takeaway
Do not treat async/await as a global fix that you sprinkle over existing code. Audit your current projects for these three specific anti-patterns. Search for await inside forEach blocks and replace them with for...of or an intentional Promise.all. Review every Promise.all that talks to external services and ask whether a single failure should really torpedo the entire operation; if not, switch to Promise.allSettled and handle the mixed results. Finally, strip the async IIFEs out of your ES module entry points and let top-level await handle your bootstrap sequence directly. These are small mechanical changes, but together they turn fragile asynchronous scripts into code you can actually trust.
