ஜாவாஸ்கிரிப்ட்டின் async context சவால்களை ஆராய்ந்து, Node.js AsyncLocalStorage மூலம் த்ரெட் பாதுகாப்பில் தேர்ச்சி பெறுங்கள். வலுவான, ஒரே நேரத்தில் இயங்கும் பயன்பாடுகளுக்கான context isolation வழிகாட்டி.
ஜாவாஸ்கிரிப்ட் Async Context & த்ரெட் பாதுகாப்பு: Context Isolation Management பற்றிய ஒரு ஆழமான பார்வை
நவீன மென்பொருள் உருவாக்க உலகில், குறிப்பாக சர்வர் பக்க பயன்பாடுகளில், நிலையை (state) நிர்வகிப்பது ஒரு அடிப்படை சவாலாகும். பல-த்ரெட் கோரிக்கை மாதிரியைக் கொண்ட மொழிகளுக்கு, த்ரெட்-லோக்கல் ஸ்டோரேஜ் ஒரு த்ரெட், ஒரு கோரிக்கை அடிப்படையில் தரவைத் தனிமைப்படுத்த ஒரு பொதுவான தீர்வை வழங்குகிறது. ஆனால் Node.js போன்ற ஒற்றை-த்ரெட், நிகழ்வு-இயக்க சூழலில் என்ன நடக்கிறது? ஒரு பரிவர்த்தனை ஐடி, பயனர் அமர்வு, அல்லது உள்ளூர்மயமாக்கல் அமைப்புகள் போன்ற கோரிக்கை-குறிப்பிட்ட சூழலை, சிக்கலான ασύγχρονη செயல்பாடுகளின் சங்கிலி முழுவதும், மற்ற ஒரே நேரத்தில் நடக்கும் கோரிக்கைகளில் கசியாமல் பாதுகாப்பாக நிர்வகிப்பது எப்படி?
இதுவே ασύγχρονη சூழல் நிர்வாகத்தின் முக்கிய பிரச்சனையாகும். இதைத் தீர்க்கத் தவறினால், குழப்பமான குறியீடு, இறுக்கமான இணைப்பு, மற்றும் மோசமான சமயங்களில், ஒரு பயனரின் கோரிக்கையிலிருந்து வரும் தரவு மற்றொருவரின் கோரிக்கையைக் கலப்படம் செய்யும் பேரழிவுப் பிழைகளுக்கு வழிவகுக்கும். இது பாரம்பரிய த்ரெட்கள் இல்லாத உலகில் 'த்ரெட் பாதுகாப்பை' அடைவது பற்றிய ஒரு கேள்வியாகும்.
இந்த விரிவான வழிகாட்டி, ஜாவாஸ்கிரிப்ட் சூழல் அமைப்பில் இந்தப் பிரச்சனையின் பரிணாமத்தை, வலிமிகுந்த கைமுறைத் தீர்வுகளிலிருந்து Node.js-ல் உள்ள `AsyncLocalStorage` API வழங்கும் நவீன, வலுவான தீர்வு வரை ஆராயும். அது எப்படி வேலை செய்கிறது, அளவிடக்கூடிய மற்றும் கவனிக்கத்தக்க அமைப்புகளை உருவாக்குவதற்கு அது ஏன் அவசியம், மற்றும் உங்கள் சொந்த பயன்பாடுகளில் அதை எவ்வாறு திறம்பட செயல்படுத்துவது என்பதை நாம் விரிவாகப் பார்ப்போம்.
சவால்: Asynchronous ஜாவாஸ்கிரிப்டில் காணாமல் போகும் Context
தீர்வை உண்மையாகப் பாராட்ட, நாம் முதலில் பிரச்சனையை ஆழமாகப் புரிந்து கொள்ள வேண்டும். ஜாவாஸ்கிரிப்டின் இயக்க மாதிரி ஒரு ஒற்றை த்ரெட் மற்றும் ஒரு நிகழ்வு வளையத்தை (event loop) அடிப்படையாகக் கொண்டது. ஒரு ασύγχρονη செயல்பாடு (ஒரு தரவுத்தள வினவல், ஒரு HTTP அழைப்பு, அல்லது ஒரு `setTimeout` போன்றவை) தொடங்கப்படும்போது, அது ஒரு தனி அமைப்புக்கு (OS கர்னல் அல்லது ஒரு த்ரெட் பூல் போன்றவை) அனுப்பப்படுகிறது. ஜாவாஸ்கிரிப்ட் த்ரெட் மற்ற குறியீட்டை இயக்க சுதந்திரமாக உள்ளது. ασύγχρονη செயல்பாடு முடிந்ததும், ஒரு கால்பேக் செயல்பாடு ஒரு வரிசையில் வைக்கப்படுகிறது, மேலும் அழைப்பு அடுக்கு (call stack) காலியாகும்போது நிகழ்வு வளையம் அதை இயக்கும்.
இந்த மாதிரி I/O-சார்ந்த பணிச்சுமைகளுக்கு நம்பமுடியாத அளவிற்கு திறமையானது, ஆனால் இது ஒரு குறிப்பிடத்தக்க சவாலை உருவாக்குகிறது: ஒரு ασύγχρονη செயல்பாட்டின் தொடக்கத்திற்கும் அதன் கால்பேக்கின் செயல்பாட்டிற்கும் இடையில் இயக்க சூழல் (execution context) இழக்கப்படுகிறது. கால்பேக், அதைத் தொடங்கிய அழைப்பு அடுக்கிலிருந்து பிரிக்கப்பட்டு, நிகழ்வு வளையத்தின் ஒரு புதிய சுற்றாக இயங்குகிறது.
ஒரு பொதுவான வலை சேவையக சூழ்நிலையுடன் இதை விளக்குவோம். ஒரு கோரிக்கையின் வாழ்க்கைச் சுழற்சியின் போது செய்யப்படும் ஒவ்வொரு செயலுடனும் ஒரு தனித்துவமான `requestID`-ஐ பதிவு செய்ய விரும்புகிறோம் என்று கற்பனை செய்து பாருங்கள்.
எளிய அணுகுமுறை (மற்றும் அது ஏன் தோல்வியடைகிறது)
Node.js-க்கு புதிய ஒரு டெவலப்பர் ஒரு குளோபல் மாறியைப் பயன்படுத்த முயற்சி செய்யலாம்:
let globalRequestID = null;
// A simulated database call
function getUserFromDB(userId) {
console.log(`[${globalRequestID}] Fetching user ${userId}`);
return new Promise(resolve => setTimeout(() => resolve({ id: userId, name: 'Jane Doe' }), 100));
}
// A simulated external service call
async function getPermissions(user) {
console.log(`[${globalRequestID}] Getting permissions for ${user.name}`);
await new Promise(resolve => setTimeout(resolve, 150));
console.log(`[${globalRequestID}] Permissions retrieved`);
return { canEdit: true };
}
// Our main request handler logic
async function handleRequest(requestID) {
globalRequestID = requestID;
console.log(`[${globalRequestID}] Starting request processing`);
const user = await getUserFromDB(123);
const permissions = await getPermissions(user);
console.log(`[${globalRequestID}] Request finished successfully`);
}
// Simulate two concurrent requests arriving at nearly the same time
console.log("Simulating concurrent requests...");
handleRequest('req-A');
handleRequest('req-B');
இந்தக் குறியீட்டை நீங்கள் இயக்கினால், வெளியீடு ஒரு சிதைந்த குழப்பமாக இருக்கும்:
Simulating concurrent requests...
[req-A] Starting request processing
[req-A] Fetching user 123
[req-B] Starting request processing
[req-B] Fetching user 123
[req-B] Getting permissions for Jane Doe
[req-B] Getting permissions for Jane Doe
[req-B] Permissions retrieved
[req-B] Request finished successfully
[req-B] Permissions retrieved
[req-B] Request finished successfully
`req-B` உடனடியாக `globalRequestID`-ஐ மேலெழுதுவதை கவனியுங்கள். `req-A`-க்கான ασύγχρονη செயல்பாடுகள் மீண்டும் தொடங்கும் நேரத்தில், குளோபல் மாறி மாற்றப்பட்டுவிட்டது, மேலும் அடுத்தடுத்த அனைத்து பதிவுகளும் தவறாக `req-B` உடன் குறிக்கப்படுகின்றன. இது ஒரு கிளாசிக் ரேஸ் கண்டிஷன் மற்றும் ஒரே நேரத்தில் இயங்கும் சூழலில் குளோபல் நிலை ஏன் பேரழிவானது என்பதற்கு ஒரு சரியான எடுத்துக்காட்டு.
வலிமிகுந்த மாற்று வழி: Prop Drilling
மிகவும் நேரடியான, மற்றும் விவாதத்திற்குரிய வகையில் மிகவும் சிரமமான தீர்வு, அழைப்புச் சங்கிலியில் உள்ள ஒவ்வொரு செயல்பாடு வழியாகவும் சூழல் பொருளை (context object) அனுப்புவதாகும். இது பெரும்பாலும் "prop drilling" என்று அழைக்கப்படுகிறது.
// context is now an explicit parameter
function getUserFromDB(userId, context) {
console.log(`[${context.requestID}] Fetching user ${userId}`);
// ...
}
async function getPermissions(user, context) {
console.log(`[${context.requestID}] Getting permissions for ${user.name}`);
// ...
}
async function handleRequest(requestID) {
const context = { requestID };
console.log(`[${context.requestID}] Starting request processing`);
const user = await getUserFromDB(123, context);
const permissions = await getPermissions(user, context);
console.log(`[${context.requestID}] Request finished successfully`);
}
இது வேலை செய்யும். இது பாதுகாப்பானது மற்றும் கணிக்கக்கூடியது. இருப்பினும், இது பெரிய குறைபாடுகளைக் கொண்டுள்ளது:
- தேவையற்ற குறியீடு (Boilerplate): உயர்-நிலை கன்ட்ரோலர் முதல் கீழ்-நிலை பயன்பாட்டுச் செயல்பாடு வரை ஒவ்வொரு செயல்பாட்டின் கையொப்பமும் `context` பொருளை ஏற்று அனுப்பும்படி மாற்றியமைக்கப்பட வேண்டும்.
- இறுக்கமான இணைப்பு (Tight Coupling): சூழல் தங்களுக்குத் தேவையில்லாத ஆனால் அழைப்புச் சங்கிலியின் ஒரு பகுதியாக இருக்கும் செயல்பாடுகள் கூட அதைப் பற்றி அறிய வேண்டிய கட்டாயத்தில் உள்ளன. இது சுத்தமான கட்டமைப்பு மற்றும் அக்கறைகளைப் பிரித்தல் கொள்கைகளை மீறுகிறது.
- பிழை ஏற்பட வாய்ப்புள்ளது (Error-Prone): ஒரு டெவலப்பர் ஒரு மட்டத்திற்குக் கீழே சூழலை அனுப்ப மறந்துவிடுவது எளிது, இது அடுத்தடுத்த அனைத்து அழைப்புகளுக்கும் சங்கிலியை உடைக்கிறது.
பல ஆண்டுகளாக, Node.js சமூகம் இந்தப் பிரச்சினையுடன் போராடியது, இது பல்வேறு நூலக அடிப்படையிலான தீர்வுகளுக்கு வழிவகுத்தது.
முன்னோடிகளும் ஆரம்பகால முயற்சிகளும்: நவீன Context நிர்வாகத்திற்கான பாதை
வழக்கொழிந்த `domain` தொகுதி
Node.js-இன் ஆரம்பகால பதிப்புகள் பிழைகளைக் கையாளவும், I/O செயல்பாடுகளைக் குழுவாக இணைக்கவும் `domain` தொகுதியை அறிமுகப்படுத்தின. இது ασύγχρονη கால்பேக்குகளை ஒரு செயலில் உள்ள "டொமைனுடன்" மறைமுகமாகப் பிணைத்தது, அது சூழல் தரவையும் வைத்திருக்க முடியும். இது நம்பிக்கைக்குரியதாகத் தோன்றினாலும், இது குறிப்பிடத்தக்க செயல்திறன் செலவைக் கொண்டிருந்தது மற்றும் நம்பகத்தன்மையற்றதாக இருந்தது, சூழல் இழக்கப்படக்கூடிய நுட்பமான விளிம்பு நிகழ்வுகளுடன். இது இறுதியில் வழக்கொழிக்கப்பட்டது மற்றும் நவீன பயன்பாடுகளில் பயன்படுத்தப்படக்கூடாது.
Continuation-Local Storage (CLS) நூலகங்கள்
சமூகம் "Continuation-Local Storage" என்ற கருத்துடன் களமிறங்கியது. `cls-hooked` போன்ற நூலகங்கள் மிகவும் பிரபலமாகின. அவை Node-இன் உள் `async_hooks` API-ஐப் பயன்படுத்தி வேலை செய்தன, இது ασύγχρονη வளங்களின் வாழ்க்கைச் சுழற்சியில் தெரிவுநிலையை வழங்குகிறது.
இந்த நூலகங்கள் தற்போதைய சூழலைக் கண்காணிக்க Node.js-இன் ασύγχρονη ப்ரிமிட்டிவ்களை பேட்ச் செய்தன அல்லது "மங்கி-பேட்ச்" செய்தன. ஒரு ασύγχρονη செயல்பாடு தொடங்கப்பட்டபோது, நூலகம் தற்போதைய சூழலைச் சேமிக்கும். அதன் கால்பேக் இயக்க திட்டமிடப்பட்டதும், நூலகம் கால்பேக்கை இயக்கும் முன் அந்த சூழலை மீட்டெடுக்கும்.
`cls-hooked` மற்றும் அதுபோன்ற நூலகங்கள் கருவியாக இருந்தபோதிலும், அவை இன்னும் ஒரு மாற்று வழியாகவே இருந்தன. அவை மாறக்கூடிய உள் API-களை நம்பியிருந்தன, அவற்றின் சொந்த செயல்திறன் தாக்கங்களைக் கொண்டிருக்கலாம், மற்றும் சில நேரங்களில் `async/await` போன்ற புதிய ஜாவாஸ்கிரிப்ட் மொழி அம்சங்களுடன் சூழலை சரியாகக் கண்காணிக்க சிரமப்பட்டன, சரியாக உள்ளமைக்கப்படாவிட்டால்.
நவீன தீர்வு: `AsyncLocalStorage`-ஐ அறிமுகப்படுத்துதல்
ஒரு நிலையான, மையத் தீர்வுக்கான முக்கியமான தேவையை உணர்ந்து, Node.js குழு `AsyncLocalStorage` API-ஐ அறிமுகப்படுத்தியது. இது Node.js v14-இல் நிலையானது மற்றும் இன்று ασύγχρονη சூழலை நிர்வகிப்பதற்கான நிலையான, பரிந்துரைக்கப்பட்ட வழியாகும். இது அதே சக்திவாய்ந்த `async_hooks` பொறிமுறையை மறைமுகமாகப் பயன்படுத்துகிறது ஆனால் ஒரு சுத்தமான, நம்பகமான, மற்றும் செயல்திறன்மிக்க பொது API-ஐ வழங்குகிறது.
`AsyncLocalStorage` உங்களை ஒரு தனிமைப்படுத்தப்பட்ட சேமிப்பக சூழலை உருவாக்க அனுமதிக்கிறது, இது ασύγχρονη செயல்பாடுகளின் முழுச் சங்கிலியிலும் நீடிக்கிறது, இது prop drilling இல்லாமல் ஒரு "கோரிக்கை-உள்ளூர்" சேமிப்பகத்தை திறம்பட உருவாக்குகிறது.
முக்கிய கருத்துக்கள் மற்றும் முறைகள்
`AsyncLocalStorage`-ஐப் பயன்படுத்துவது ஒரு சில முக்கிய முறைகளைச் சுற்றியே உள்ளது:
new AsyncLocalStorage(): நீங்கள் வகுப்பின் ஒரு நிகழ்வை உருவாக்குவதன் மூலம் தொடங்குகிறீர்கள். பொதுவாக, நீங்கள் ஒரு குறிப்பிட்ட வகை சூழலுக்கு (எ.கா., அனைத்து HTTP கோரிக்கைகளுக்கும் ஒன்று) ஒரு ஒற்றை நிகழ்வை உருவாக்கி அதை ஒரு பகிரப்பட்ட தொகுதியிலிருந்து ஏற்றுமதி செய்கிறீர்கள்..run(store, callback): இது நுழைவுப் புள்ளி. இது இரண்டு வாதங்களை எடுக்கிறது: ஒரு `store` (நீங்கள் கிடைக்கச் செய்ய விரும்பும் தரவு) மற்றும் ஒரு `callback` செயல்பாடு. இது கால்பேக்கை உடனடியாக இயக்குகிறது, மேலும் அந்த கால்பேக்கின் செயல்பாட்டின் முழு ஒத்திசைவான மற்றும் ασύγχρονη காலத்திற்கும், வழங்கப்பட்ட `store` அணுகக்கூடியதாக இருக்கும்..getStore(): தரவை நீங்கள் இப்படித்தான் மீட்டெடுக்கிறீர்கள். `.run()`-ஆல் தொடங்கப்பட்ட ασύγχρονη ஓட்டத்தின் ஒரு பகுதியாக இருக்கும் ஒரு செயல்பாட்டிற்குள் இருந்து அழைக்கப்படும்போது, அது அந்த சூழலுடன் தொடர்புடைய `store` பொருளைத் திருப்பித் தரும். அத்தகைய சூழலுக்கு வெளியே அழைக்கப்பட்டால், அது `undefined`-ஐத் திருப்பித் தரும்.
நமது முந்தைய உதாரணத்தை `AsyncLocalStorage`-ஐப் பயன்படுத்தி மீண்டும் கட்டமைப்போம்.
const { AsyncLocalStorage } = require('async_hooks');
// 1. Create a single, shared instance
const asyncLocalStorage = new AsyncLocalStorage();
// 2. Our functions no longer need a 'context' parameter
function getUserFromDB(userId) {
const store = asyncLocalStorage.getStore();
console.log(`[${store.requestID}] Fetching user ${userId}`);
return new Promise(resolve => setTimeout(() => resolve({ id: userId, name: 'Jane Doe' }), 100));
}
async function getPermissions(user) {
const store = asyncLocalStorage.getStore();
console.log(`[${store.requestID}] Getting permissions for ${user.name}`);
await new Promise(resolve => setTimeout(resolve, 150));
console.log(`[${store.requestID}] Permissions retrieved`);
return { canEdit: true };
}
async function businessLogic() {
const store = asyncLocalStorage.getStore();
console.log(`[${store.requestID}] Starting request processing`);
const user = await getUserFromDB(123);
const permissions = await getPermissions(user);
console.log(`[${store.requestID}] Request finished successfully`);
}
// 3. The main request handler uses .run() to establish the context
function handleRequest(requestID) {
const context = { requestID };
asyncLocalStorage.run(context, () => {
// Everything called from here, sync or async, has access to the context
businessLogic();
});
}
console.log("Simulating concurrent requests with AsyncLocalStorage...");
handleRequest('req-A');
handleRequest('req-B');
வெளியீடு இப்போது முற்றிலும் சரியாகவும் தனிமைப்படுத்தப்பட்டதாகவும் உள்ளது:
Simulating concurrent requests with AsyncLocalStorage...
[req-A] Starting request processing
[req-A] Fetching user 123
[req-B] Starting request processing
[req-B] Fetching user 123
[req-A] Getting permissions for Jane Doe
[req-B] Getting permissions for Jane Doe
[req-A] Permissions retrieved
[req-A] Request finished successfully
[req-B] Permissions retrieved
[req-B] Request finished successfully
சுத்தமான பிரிவினையைக் கவனியுங்கள். `getUserFromDB` மற்றும் `getPermissions` செயல்பாடுகள் சுத்தமாக உள்ளன; அவற்றிடம் `context` அளவுரு இல்லை. `getStore()` வழியாக அவர்களுக்குத் தேவைப்படும்போது அவர்கள் சூழலைக் கோரலாம். சூழல் கோரிக்கையின் நுழைவுப் புள்ளியில் (`handleRequest`) ஒருமுறை நிறுவப்பட்டு, முழு ασύγχρονη சங்கிலி வழியாகவும் மறைமுகமாக எடுத்துச் செல்லப்படுகிறது.
நடைமுறைச் செயலாக்கம்: Express.js உடன் ஒரு நிஜ-உலக உதாரணம்
கோரிக்கை-நோக்கிய சூழலை நிர்வகிக்க Express.js போன்ற வலை சேவையக கட்டமைப்புகளில் `AsyncLocalStorage`-க்கான மிகவும் சக்திவாய்ந்த பயன்பாட்டு நிகழ்வுகளில் ஒன்றாகும். ஒரு நடைமுறை உதாரணத்தை உருவாக்குவோம்.
காட்சி
எங்களிடம் ஒரு வலை பயன்பாடு உள்ளது, அது செய்ய வேண்டியவை:
- ஒவ்வொரு உள்வரும் கோரிக்கைக்கும் கண்டறியும் தன்மைக்காக ஒரு தனித்துவமான `requestID`-ஐ ஒதுக்க வேண்டும்.
- ஒவ்வொரு பதிவுச் செய்தியிலும் இந்த `requestID`-ஐ கைமுறையாக அனுப்பாமல் தானாகவே சேர்க்கும் ஒரு மையப்படுத்தப்பட்ட பதிவுச் சேவையைக் கொண்டிருக்க வேண்டும்.
- அங்கீகாரத்திற்குப் பிறகு பயனர் தகவல்களை கீழ்நிலை சேவைகளுக்குக் கிடைக்கச் செய்ய வேண்டும்.
படி 1: ஒரு மைய சூழல் சேவையை உருவாக்குதல்
`AsyncLocalStorage` நிகழ்வை நிர்வகிக்கும் ஒரு ஒற்றைத் தொகுதியை உருவாக்குவது சிறந்த நடைமுறையாகும்.
கோப்பு: `context.js`
const { AsyncLocalStorage } = require('async_hooks');
// This instance is shared across the entire application
const requestContext = new AsyncLocalStorage();
module.exports = { requestContext };
படி 2: சூழலை நிறுவ ஒரு Middleware உருவாக்குதல்
Express-இல், முழு கோரிக்கை வாழ்க்கைச் சுழற்சியையும் சுற்றிவளைக்க `.run()`-ஐப் பயன்படுத்த Middleware சரியான இடம்.
கோப்பு: `app.js` (அல்லது உங்கள் முக்கிய சர்வர் கோப்பு)
const express = require('express');
const { v4: uuidv4 } = require('uuid');
const { requestContext } = require('./context');
const logger = require('./logger');
const userService = require('./userService');
const app = express();
// Middleware to establish the async context for each request
app.use((req, res, next) => {
const store = {
requestID: uuidv4(),
user: null // Will be populated after authentication
};
// .run() wraps the rest of the request handling (next())
requestContext.run(store, () => {
logger.info(`Request started: ${req.method} ${req.url}`);
next();
});
});
// A simulated authentication middleware
app.use((req, res, next) => {
// In a real app, you'd verify a token here
const store = requestContext.getStore();
if (store) {
store.user = { id: 'user-123', name: 'Alice' };
}
next();
});
// Your application routes
app.get('/user', async (req, res) => {
logger.info('Handling /user request');
try {
const userProfile = await userService.getProfile();
res.json(userProfile);
} catch (error) {
logger.error('Failed to get user profile', { error: error.message });
res.status(500).send('Internal Server Error');
}
});
const PORT = 3000;
app.listen(PORT, () => {
console.log(`Server running on http://localhost:${PORT}`);
});
படி 3: சூழலைத் தானாகப் பயன்படுத்தும் ஒரு Logger
இங்குதான் மாயாஜாலம் நடக்கிறது. நமது logger, Express, கோரிக்கைகள், அல்லது பயனர்கள் பற்றி முற்றிலும் அறியாமல் இருக்கலாம். அது நமது மைய சூழல் சேவையைப் பற்றி மட்டுமே அறியும்.
கோப்பு: `logger.js`
const { requestContext } = require('./context');
function log(level, message, details = {}) {
const store = requestContext.getStore();
const requestID = store ? store.requestID : 'N/A';
const logObject = {
timestamp: new Date().toISOString(),
level: level.toUpperCase(),
requestID,
message,
...details
};
console.log(JSON.stringify(logObject));
}
const logger = {
info: (message, details) => log('info', message, details),
error: (message, details) => log('error', message, details),
warn: (message, details) => log('warn', message, details),
};
module.exports = logger;
படி 4: சூழலை அணுகும் ஒரு ஆழமாக உள்ளமைக்கப்பட்ட சேவை
நமது `userService` இப்போது கன்ட்ரோலரிலிருந்து எந்த அளவுருக்களும் அனுப்பப்படாமல் கோரிக்கை-குறிப்பிட்ட தகவல்களை நம்பிக்கையுடன் அணுக முடியும்.
கோப்பு: `userService.js`
const { requestContext } = require('./context');
const logger = require('./logger');
// A simulated database call
async function fetchUserDetailsFromDB(userId) {
logger.info(`Fetching details for user ${userId} from database.`);
await new Promise(resolve => setTimeout(resolve, 50));
return { company: 'Global Tech Inc.', country: 'Worldwide' };
}
async function getProfile() {
const store = requestContext.getStore();
if (!store || !store.user) {
throw new Error('User not authenticated');
}
logger.info(`Building profile for user: ${store.user.name}`);
// Even deeper async calls will maintain context
const details = await fetchUserDetailsFromDB(store.user.id);
return {
id: store.user.id,
name: store.user.name,
...details
};
}
module.exports = { getProfile };
இந்த சர்வரை இயக்கி, `http://localhost:3000/user`-க்கு ஒரு கோரிக்கை விடுத்தால், உங்கள் கன்சோல் பதிவுகள் தெளிவாகக் காண்பிக்கும், அதே `requestID` ஆரம்ப மிடில்வேரிலிருந்து ஆழமான தரவுத்தள செயல்பாடு வரை ஒவ்வொரு பதிவுச் செய்தியிலும் உள்ளது, இது சரியான சூழல் தனிமைப்படுத்தலைக் காட்டுகிறது.
த்ரெட் பாதுகாப்பு மற்றும் சூழல் தனிமைப்படுத்தல் விளக்கப்பட்டது
இப்போது நாம் "த்ரெட் பாதுகாப்பு" என்ற சொல்லுக்குத் திரும்பலாம். Node.js-இல், கவலை என்பது பல த்ரெட்கள் ஒரே நினைவகத்தை ஒரே நேரத்தில் உண்மையான இணையான முறையில் அணுகுவது பற்றியது அல்ல. மாறாக, இது பல ஒரே நேரத்தில் இயங்கும் செயல்பாடுகள் (கோரிக்கைகள்) நிகழ்வு வளையம் வழியாக ஒற்றை முக்கிய த்ரெட்டில் தங்கள் செயல்பாட்டை ஒன்றோடொன்று கலப்பது பற்றியது. "பாதுகாப்பு" பிரச்சினை என்பது ஒரு செயல்பாட்டின் சூழல் மற்றொன்றுக்குள் கசியாமல் இருப்பதை உறுதி செய்வதாகும்.
`AsyncLocalStorage` சூழலை ασύγχρονη வளங்களுடன் இணைப்பதன் மூலம் இதைச் செய்கிறது.
என்ன நடக்கிறது என்பதற்கான ஒரு எளிமைப்படுத்தப்பட்ட மன மாதிரி இங்கே:
- `asyncLocalStorage.run(store, ...)` அழைக்கப்படும்போது, Node.js உள்நாட்டில் கூறுகிறது: "நான் இப்போது ஒரு சிறப்புச் சூழலுக்குள் நுழைகிறேன். இந்தச் சூழலுக்கான தரவு `store`." இது இந்த இயக்க சூழலுக்கு ஒரு தனித்துவமான உள் ஐடியை ஒதுக்குகிறது.
- இந்தச் சூழல் செயலில் இருக்கும்போது திட்டமிடப்பட்ட எந்தவொரு ασύγχρονη செயல்பாடும் (எ.கா., ஒரு `new Promise`, `setTimeout`, `fs.readFile`) இந்த தனித்துவமான சூழல் ஐடியுடன் குறிக்கப்படுகிறது.
- பின்னர், நிகழ்வு வளையம் இந்த குறிக்கப்பட்ட செயல்பாடுகளில் ஒன்றிற்கான ஒரு கால்பேக்கைத் தேர்ந்தெடுக்கும்போது, Node.js குறியீட்டைச் சரிபார்க்கிறது. அது கூறுகிறது, "ஆ, இந்த கால்பேக் சூழல் ஐடி X-க்கு சொந்தமானது. கால்பேக்கை இயக்கும் முன் நான் இப்போது அந்த சூழலை மீட்டெடுப்பேன்."
- இந்த மீட்டெடுப்பு கால்பேக்கிற்குள் `getStore()`-க்கு சரியான `store`-ஐக் கிடைக்கச் செய்கிறது.
- மற்றொரு கோரிக்கை வரும்போது, அதன் `.run()`-க்கான அழைப்பு வேறுபட்ட உள் ஐடியுடன் முற்றிலும் புதிய சூழலை உருவாக்குகிறது, மேலும் அதன் ασύγχρονη செயல்பாடுகள் இந்த புதிய ஐடியுடன் குறிக்கப்படுகின்றன, இது பூஜ்ஜிய மேல்பொருந்துதலை உறுதி செய்கிறது.
இந்த வலுவான, கீழ்-நிலை பொறிமுறை, நிகழ்வு வளையம் வெவ்வேறு கோரிக்கைகளிலிருந்து வரும் கால்பேக்குகளின் செயல்பாட்டை எப்படி ஒன்றோடொன்று கலந்தாலும், `getStore()` எப்போதும் அதன் கால்பேக்கின் ασύγχρονη செயல்பாடு முதலில் திட்டமிடப்பட்ட சூழலுக்கான தரவைத் திருப்பித் தரும் என்பதை உறுதி செய்கிறது.
செயல்திறன் பரிசீலனைகள் மற்றும் சிறந்த நடைமுறைகள்
`AsyncLocalStorage` மிகவும் உகந்ததாக இருந்தாலும், அது இலவசம் அல்ல. அதன் அடிப்படையிலான `async_hooks` ஒவ்வொரு ασύγχρονη வளத்தின் உருவாக்கம் மற்றும் நிறைவுக்கு ஒரு சிறிய அளவு கூடுதல் சுமையைச் சேர்க்கிறது. இருப்பினும், பெரும்பாலான பயன்பாடுகளுக்கு, குறிப்பாக I/O-சார்ந்தவற்றுக்கு, குறியீட்டின் தெளிவு, பராமரிப்புத்திறன், மற்றும் கவனிக்கத்தக்கத்தன்மையில் கிடைக்கும் நன்மைகளுடன் ஒப்பிடும்போது இந்த கூடுதல் சுமை மிகக் குறைவு.
- ஒருமுறை துவக்கவும்: உங்கள் `AsyncLocalStorage` நிகழ்வுகளை உங்கள் பயன்பாட்டின் மேல் மட்டத்தில் உருவாக்கி அவற்றை மீண்டும் பயன்படுத்தவும். ஒவ்வொரு கோரிக்கைக்கும் புதிய நிகழ்வுகளை உருவாக்க வேண்டாம்.
- Store-ஐ சிறியதாக வைத்திருங்கள்: சூழல் ஸ்டோர் ஒரு தற்காலிக சேமிப்பகம் (cache) அல்ல. ஐடிகள், டோக்கன்கள், அல்லது இலகுவான பயனர் பொருள்கள் போன்ற சிறிய, அத்தியாவசியத் தரவுகளுக்கு இதைப் பயன்படுத்தவும். பெரிய பேலோடுகளைச் சேமிப்பதைத் தவிர்க்கவும்.
- தெளிவான நுழைவுப் புள்ளிகளில் சூழலை நிறுவவும்: ஒரு சுயாதீனமான ασύγχρονη ஓட்டத்தின் உறுதியான தொடக்கத்தில் `.run()`-ஐ அழைப்பது சிறந்த இடங்கள். இதில் சர்வர் கோரிக்கை மிடில்வேர், செய்தி வரிசை நுகர்வோர், அல்லது பணி திட்டமிடுபவர்கள் அடங்குவர்.
- Fire-and-Forget செயல்பாடுகளைப் பற்றி கவனமாக இருங்கள்: நீங்கள் ஒரு `run` சூழலுக்குள் ஒரு ασύγχρονη செயல்பாட்டைத் தொடங்கி, அதை `await` செய்யாவிட்டால் (எ.கா., `doSomething().catch(...)`), அது இன்னும் சூழலைச் சரியாகப் பெறும். இது அவற்றின் மூலத்திற்குத் திரும்பக் கண்டுபிடிக்கப்பட வேண்டிய பின்னணிப் பணிகளுக்கு ஒரு சக்திவாய்ந்த அம்சமாகும்.
- கூடு கட்டுதலைப் (Nesting) புரிந்து கொள்ளுங்கள்: நீங்கள் `.run()`-க்கான அழைப்புகளைக் கூடு கட்டலாம். ஏற்கனவே உள்ள சூழலுக்குள் `.run()`-ஐ அழைப்பது ஒரு புதிய, கூடு கட்டப்பட்ட சூழலை உருவாக்கும். `getStore()` அப்போது உள்สุด ஸ்டோரைத் திருப்பித் தரும். இது ஒரு குறிப்பிட்ட துணை-செயல்பாட்டிற்காக சூழலைத் தற்காலிகமாக மேலெழுத அல்லது சேர்க்கப் பயன்படும்.
Node.js-க்கு அப்பால்: `AsyncContext` உடன் எதிர்காலம்
அசிங்க்ரோனஸ் சூழல் நிர்வாகத்தின் தேவை Node.js-க்கு மட்டும் உரியது அல்ல. முழு ஜாவாஸ்கிரிப்ட் சூழல் அமைப்புக்கும் அதன் முக்கியத்துவத்தை உணர்ந்து, `AsyncContext` எனப்படும் ஒரு முறையான முன்மொழிவு, ஜாவாஸ்கிரிப்டை (ECMAScript) தரப்படுத்தும் TC39 குழுவின் மூலம் முன்னேறி வருகிறது.
`AsyncContext` முன்மொழிவு Node.js-இன் `AsyncLocalStorage`-ஆல் பெரிதும் ஈர்க்கப்பட்டு, வலை உலாவிகள் உட்பட அனைத்து நவீன ஜாவாஸ்கிரிப்ட் சூழல்களிலும் கிடைக்கும் ஏறக்குறைய ஒரே மாதிரியான API-ஐ வழங்குவதை நோக்கமாகக் கொண்டுள்ளது. இது முன்-முனை மேம்பாட்டிற்கு சக்திவாய்ந்த திறன்களைத் திறக்கக்கூடும், அதாவது ஒரே நேரத்தில் ரெண்டரிங் செய்யும் போது React போன்ற சிக்கலான கட்டமைப்புகளில் சூழலை நிர்வகிப்பது அல்லது சிக்கலான கூறு மரங்கள் முழுவதும் பயனர் தொடர்பு ஓட்டங்களைக் கண்காணிப்பது போன்றவை.
முடிவு: அறிவிப்புரீதியான மற்றும் வலுவான அசிங்க்ரோனஸ் குறியீட்டைத் தழுவுதல்
அசிங்க்ரோனஸ் செயல்பாடுகள் முழுவதும் நிலையை நிர்வகிப்பது என்பது பல ஆண்டுகளாக ஜாவாஸ்கிரிப்ட் டெவலப்பர்களுக்கு சவாலாக இருந்த ஒரு ஏமாற்றும் சிக்கலான பிரச்சனையாகும். கைமுறை prop drilling மற்றும் பலவீனமான சமூக நூலகங்களிலிருந்து `AsyncLocalStorage` வடிவில் ஒரு மைய, நிலையான API-க்கு வந்த பயணம் Node.js தளத்தின் குறிப்பிடத்தக்க முதிர்ச்சியைக் குறிக்கிறது.
பாதுகாப்பான, தனிமைப்படுத்தப்பட்ட, மற்றும் மறைமுகமாகப் பரப்பப்படும் சூழலுக்கான ஒரு பொறிமுறையை வழங்குவதன் மூலம், `AsyncLocalStorage` நம்மை சுத்தமான, மேலும் பிரிக்கப்பட்ட, மற்றும் மேலும் பராமரிக்கக்கூடிய குறியீட்டை எழுத உதவுகிறது. இது நவீன, கவனிக்கத்தக்க அமைப்புகளை உருவாக்குவதற்கான ஒரு மூலக்கல்லாகும், அங்கு தடமறிதல், கண்காணிப்பு, மற்றும் பதிவுசெய்தல் ஆகியவை பின்தொடரும் எண்ணங்களாக இல்லாமல், பயன்பாட்டின் கட்டமைப்பில் பிணைக்கப்பட்டுள்ளன.
நீங்கள் ஒரே நேரத்தில் செயல்பாடுகளைக் கையாளும் எந்தவொரு அற்பமற்ற Node.js பயன்பாட்டையும் உருவாக்குகிறீர்கள் என்றால், `AsyncLocalStorage`-ஐத் தழுவுவது இனி ஒரு சிறந்த நடைமுறை மட்டுமல்ல - இது ஒரு அசிங்க்ரோனஸ் உலகில் வலுவையும் அளவிடுதலையும் அடைவதற்கான ஒரு அடிப்படை நுட்பமாகும்.