Õppige selgeks JavaScripti Promise'i kombinaatorid (Promise.all, Promise.allSettled, Promise.race, Promise.any), et saavutada tõhus ja töökindel asünkroonne programmeerimine globaalsetes rakendustes.
JavaScripti Promise'i kombinaatorid: täiustatud asünkroonsed mustrid globaalsete rakenduste jaoks
Asünkroonne programmeerimine on kaasaegse JavaScripti nurgakivi, eriti veebirakenduste loomisel, mis suhtlevad API-de, andmebaasidega või teostavad aeganõudvaid operatsioone. JavaScripti Promise'id pakuvad võimsa abstraktsiooni asünkroonsete operatsioonide haldamiseks, kuid nende valdamine nõuab täiustatud mustrite mõistmist. See artikkel süveneb JavaScripti Promise'i kombinaatoritesse – Promise.all, Promise.allSettled, Promise.race ja Promise.any – ning sellesse, kuidas neid saab kasutada tõhusate ja töökindlate asünkroonsete töövoogude loomiseks, eriti globaalsete rakenduste kontekstis, kus on erinevad võrgutingimused ja andmeallikad.
Promise'ide mõistmine: kiire ülevaade
Enne kombinaatoritesse süvenemist vaatame kiirelt üle Promise'id. Promise esindab asünkroonse operatsiooni lõpptulemust. See võib olla ühes kolmest olekust:
- Ootel (Pending): Algne olek, ei ole täidetud ega tagasi lükatud.
- Täidetud (Fulfilled): Operatsioon lõppes edukalt, tulemuseks on väärtus.
- Tagasi lükatud (Rejected): Operatsioon ebaõnnestus põhjusega (tavaliselt Error objekt).
Promise'id pakuvad puhtamat ja paremini hallatavat viisi asünkroonsete operatsioonide käsitlemiseks võrreldes traditsiooniliste tagasikutsetega (callbacks). Need parandavad koodi loetavust ja lihtsustavad veakäsitlust. Mis on oluline, nad moodustavad ka aluse Promise'i kombinaatoritele, mida me uurima hakkame.
Promise'i kombinaatorid: asünkroonsete operatsioonide orkestreerimine
Promise'i kombinaatorid on staatilised meetodid Promise objektil, mis võimaldavad teil hallata ja koordineerida mitut Promise'i. Need pakuvad võimsaid tööriistu keerukate asünkroonsete töövoogude loomiseks. Uurime igaüht neist üksikasjalikult.
Promise.all(): Promise'ide paralleelne täitmine ja tulemuste koondamine
Promise.all() võtab sisendiks itereeritava objekti (tavaliselt massiivi) Promise'idest ja tagastab üheainsa Promise'i. See tagastatud Promise täitub siis, kui kõik sisend-Promise'id on täitunud. Kui mõni sisend-Promise tagasi lükatakse, lükatakse tagastatud Promise kohe tagasi esimese tagasi lükatud Promise'i põhjusega.
Kasutusjuht: Kui teil on vaja samaaegselt tuua andmeid mitmest API-st ja töödelda kombineeritud tulemusi, on Promise.all() ideaalne. Näiteks kujutage ette armatuurlaua ehitamist, mis kuvab ilmateavet erinevatest maailma linnadest. Iga linna andmed võiks tuua eraldi API-kutsega.
async function fetchWeatherData(city) {
try {
const response = await fetch(`https://api.example.com/weather?city=${city}`); // Asenda päris API otspunktiga
if (!response.ok) {
throw new Error(`Failed to fetch weather data for ${city}`);
}
return await response.json();
} catch (error) {
console.error(`Error fetching weather data for ${city}: ${error}`);
throw error; // Viska viga uuesti, et Promise.all saaks selle kinni püüda
}
}
async function displayWeatherData() {
const cities = ['London', 'Tokyo', 'New York', 'Sydney'];
try {
const weatherDataPromises = cities.map(city => fetchWeatherData(city));
const weatherData = await Promise.all(weatherDataPromises);
weatherData.forEach((data, index) => {
console.log(`Weather in ${cities[index]}:`, data);
// Uuenda kasutajaliidest ilmaandmetega
});
} catch (error) {
console.error('Failed to fetch weather data for all cities:', error);
// Kuva kasutajale veateade
}
}
displayWeatherData();
Kaalutlused globaalsete rakenduste jaoks:
- Võrgu latentsus: Erinevates geograafilistes asukohtades asuvatele erinevatele API-dele tehtud päringutel võib olla erinev latentsus.
Promise.all()ei garanteeri järjekorda, milles Promise'id täituvad, vaid ainult seda, et nad kõik täituvad (või üks lükatakse tagasi) enne, kui kombineeritud Promise laheneb. - API päringute piirangud (Rate Limiting): Kui teete mitu päringut samale API-le või mitmele API-le, millel on jagatud päringute piirangud, võite need piirangud ületada. Rakendage strateegiaid, nagu päringute järjekorda panemine või eksponentsiaalse taganemise kasutamine, et piirangutega sujuvalt toime tulla.
- Veakäsitlus: Pidage meeles, et kui ükskõik milline Promise tagasi lükatakse, ebaõnnestub kogu
Promise.all()operatsioon. See ei pruugi olla soovitav, kui soovite kuvada osalisi andmeid isegi siis, kui mõned päringud ebaõnnestuvad. Kaaluge sellistel juhtudelPromise.allSettled()kasutamist (selgitatud allpool).
Promise.allSettled(): Edu ja ebaõnnestumise individuaalne käsitlemine
Promise.allSettled() on sarnane Promise.all()-ga, kuid olulise erinevusega: see ootab, kuni kõik sisend-Promise'id on lahenenud, olenemata sellest, kas need täituvad või lükatakse tagasi. Tagastatud Promise täitub alati objektide massiiviga, millest igaüks kirjeldab vastava sisend-Promise'i tulemust. Igal objektil on status omadus (kas "fulfilled" või "rejected") ja value (kui täidetud) või reason (kui tagasi lükatud) omadus.
Kasutusjuht: Kui teil on vaja koguda tulemusi mitmest asünkroonsest operatsioonist ja on vastuvõetav, et mõned neist ebaõnnestuvad ilma kogu operatsiooni ebaõnnestumist põhjustamata, on Promise.allSettled() parem valik. Kujutage ette süsteemi, mis töötleb makseid läbi mitme maksevärava. Võib-olla soovite proovida kõiki makseid ning salvestada, millised õnnestusid ja millised ebaõnnestusid.
async function processPayment(paymentGateway, amount) {
try {
const response = await paymentGateway.process(amount); // Asenda päris maksevärava integratsiooniga
if (response.status === 'success') {
return { status: 'fulfilled', value: `Payment processed successfully via ${paymentGateway.name}` };
} else {
throw new Error(`Payment failed via ${paymentGateway.name}: ${response.message}`);
}
} catch (error) {
return { status: 'rejected', reason: `Payment failed via ${paymentGateway.name}: ${error.message}` };
}
}
async function processMultiplePayments(paymentGateways, amount) {
const paymentPromises = paymentGateways.map(gateway => processPayment(gateway, amount));
const results = await Promise.allSettled(paymentPromises);
results.forEach((result, index) => {
if (result.status === 'fulfilled') {
console.log(result.value);
} else {
console.error(result.reason);
}
});
// Analüüsi tulemusi, et määrata kindlaks üldine edu/ebaõnnestumine
const successfulPayments = results.filter(result => result.status === 'fulfilled').length;
const failedPayments = results.filter(result => result.status === 'rejected').length;
console.log(`Successful payments: ${successfulPayments}`);
console.log(`Failed payments: ${failedPayments}`);
}
// Näidismakseväravad
const paymentGateways = [
{ name: 'PayPal', process: (amount) => Promise.resolve({ status: 'success', message: 'Payment successful' }) },
{ name: 'Stripe', process: (amount) => Promise.reject({ status: 'error', message: 'Insufficient funds' }) },
{ name: 'Worldpay', process: (amount) => Promise.resolve({ status: 'success', message: 'Payment successful' }) },
];
processMultiplePayments(paymentGateways, 100);
Kaalutlused globaalsete rakenduste jaoks:
- Töökindlus:
Promise.allSettled()suurendab teie rakenduste töökindlust, tagades, et kõik asünkroonsed operatsioonid proovitakse läbi, isegi kui mõned neist ebaõnnestuvad. See on eriti oluline hajutatud süsteemides, kus rikked on tavalised. - Detailne aruandlus: Tulemuste massiiv pakub detailset teavet iga operatsiooni tulemuse kohta, võimaldades teil logida vigu, proovida ebaõnnestunud operatsioone uuesti või anda kasutajatele konkreetset tagasisidet.
- Osaline edu: Saate hõlpsasti määrata üldise edukuse määra ja võtta vastavaid meetmeid edukate ja ebaõnnestunud operatsioonide arvu põhjal. Näiteks võite pakkuda alternatiivseid makseviise, kui esmane maksevärav ebaõnnestub.
Promise.race(): Kiireima tulemuse valimine
Promise.race() võtab samuti sisendiks itereeritava objekti Promise'idest ja tagastab üheainsa Promise'i. Erinevalt Promise.all()-st ja Promise.allSettled()-st laheneb Promise.race() aga kohe, kui ükskõik milline sisend-Promise'idest laheneb (kas täitub või lükatakse tagasi). Tagastatud Promise täitub või lükatakse tagasi esimese lahenenud Promise'i väärtuse või põhjusega.
Kasutusjuht: Kui teil on vaja valida kiireim vastus mitmest allikast, on Promise.race() hea valik. Kujutage ette, et pärite samu andmeid mitmest serverist ja kasutate esimest vastust, mille saate. See võib parandada jõudlust ja reageerimisvõimet, eriti olukordades, kus mõned serverid võivad olla ajutiselt kättesaamatud või aeglasemad kui teised.
async function fetchDataFromServer(serverURL) {
try {
const response = await fetch(serverURL, {signal: AbortSignal.timeout(5000)}); //Lisa 5-sekundiline ajalõpp
if (!response.ok) {
throw new Error(`Failed to fetch data from ${serverURL}`);
}
return await response.json();
} catch (error) {
console.error(`Error fetching data from ${serverURL}: ${error}`);
throw error;
}
}
async function getFastestResponse() {
const serverURLs = [
'https://server1.example.com/data', // Asenda päris serveri URL-idega
'https://server2.example.com/data',
'https://server3.example.com/data',
];
try {
const dataPromises = serverURLs.map(serverURL => fetchDataFromServer(serverURL));
const fastestData = await Promise.race(dataPromises);
console.log('Fastest data received:', fastestData);
// Kasuta kiireimaid andmeid
} catch (error) {
console.error('Failed to get data from any server:', error);
// Käsitle viga
}
}
getFastestResponse();
Kaalutlused globaalsete rakenduste jaoks:
- Ajalõpud: On ülioluline rakendada ajalõppe
Promise.race()kasutamisel, et vältida tagastatud Promise'i lõputut ootamist, kui mõned sisend-Promise'id ei lahene kunagi. Ülaltoodud näide kasutab selleksAbortSignal.timeout(). - Võrgutingimused: Kiireim server võib varieeruda sõltuvalt kasutaja geograafilisest asukohast ja võrgutingimustest. Kaaluge sisu edastamise võrgu (CDN) kasutamist, et levitada oma sisu ja parandada jõudlust kasutajatele üle maailma.
- Veakäsitlus: Kui Promise, mis võistluse 'võidab', lükatakse tagasi, siis lükatakse tagasi kogu Promise.race. Veenduge, et igal Promise'il on asjakohane veakäsitlus, et vältida ootamatuid tagasilükkamisi. Samuti, kui "võitnud" Promise lükatakse tagasi ajalõpu tõttu (nagu ülal näidatud), jätkavad teised Promise'id taustal töötamist. Võib-olla peate lisama loogika nende teiste Promise'ide tühistamiseks, kasutades
AbortController'it, kui neid enam ei vajata.
Promise.any(): Esimese täitumise aktsepteerimine
Promise.any() on sarnane Promise.race()-ga, kuid veidi erineva käitumisega. See ootab, kuni esimene sisend-Promise täitub. Kui kõik sisend-Promise'id lükatakse tagasi, lükkab Promise.any() tagasi AggregateError-iga, mis sisaldab tagasilükkamise põhjuste massiivi.
Kasutusjuht: Kui teil on vaja hankida andmeid mitmest allikast ja teid huvitab ainult esimene edukas tulemus, on Promise.any() hea valik. See on kasulik, kui teil on üleliigseid andmeallikaid või alternatiivseid API-sid, mis pakuvad sama teavet. See eelistab edu kiirusele, kuna ootab esimest täitumist, isegi kui mõned Promise'id lükatakse kiiresti tagasi.
async function fetchDataFromSource(sourceURL) {
try {
const response = await fetch(sourceURL);
if (!response.ok) {
throw new Error(`Failed to fetch data from ${sourceURL}`);
}
return await response.json();
} catch (error) {
console.error(`Error fetching data from ${sourceURL}: ${error}`);
throw error;
}
}
async function getFirstSuccessfulData() {
const dataSources = [
'https://source1.example.com/data', // Asenda päris andmeallika URL-idega
'https://source2.example.com/data',
'https://source3.example.com/data',
];
try {
const dataPromises = dataSources.map(sourceURL => fetchDataFromSource(sourceURL));
const data = await Promise.any(dataPromises);
console.log('First successful data received:', data);
// Kasuta edukaid andmeid
} catch (error) {
if (error instanceof AggregateError) {
console.error('Failed to get data from any source:', error.errors);
// Käsitle viga
} else {
console.error('An unexpected error occurred:', error);
}
}
}
getFirstSuccessfulData();
Kaalutlused globaalsete rakenduste jaoks:
- Üleliigsus:
Promise.any()on eriti kasulik tegelemisel üleliigsete andmeallikatega, mis pakuvad sarnast teavet. Kui üks allikas on kättesaamatu või aeglane, saate toetuda teistele andmete pakkumisel. - Veakäsitlus: Kindlasti käsitlege
AggregateError'it, mis visatakse siis, kui kõik sisend-Promise'id lükatakse tagasi. See viga sisaldab massiivi individuaalsetest tagasilükkamise põhjustest, mis võimaldab teil probleeme siluda ja diagnoosida. - Prioritiseerimine: Järjekord, milles te Promise'id
Promise.any()-le annate, on oluline. Asetage kõige usaldusväärsemad või kiireimad andmeallikad esimesena, et suurendada eduka tulemuse tõenäosust.
Õige kombinaatori valimine: kokkuvõte
Siin on lühike kokkuvõte, mis aitab teil valida oma vajadustele vastava Promise'i kombinaatori:
- Promise.all(): Kasutage siis, kui vajate, et kõik Promise'id täituksid edukalt ja soovite kohe ebaõnnestuda, kui mõni Promise tagasi lükatakse.
- Promise.allSettled(): Kasutage siis, kui soovite oodata, kuni kõik Promise'id on lahenenud, olenemata edust või ebaõnnestumisest, ja vajate detailset teavet iga tulemuse kohta.
- Promise.race(): Kasutage siis, kui soovite valida kiireima tulemuse mitme Promise'i hulgast ja teid huvitab ainult esimene, mis laheneb.
- Promise.any(): Kasutage siis, kui soovite aktsepteerida esimest edukat tulemust mitme Promise'i hulgast ja teid ei häiri, kui mõned Promise'id tagasi lükatakse.
Täiustatud mustrid ja parimad praktikad
Lisaks Promise'i kombinaatorite põhilisele kasutusele on mitmeid täiustatud mustreid ja parimaid praktikaid, mida meeles pidada:
Konkurentsuse piiramine
Suure hulga Promise'idega tegelemisel võib nende kõigi paralleelne täitmine teie süsteemi üle koormata või ületada API päringute piiranguid. Saate piirata konkurentsust, kasutades tehnikaid nagu:
- Tükeldamine (Chunking): Jagage Promise'id väiksemateks tükkideks ja töödelge iga tükki järjestikku.
- Semafori kasutamine: Rakendage semafor, et kontrollida samaaegsete operatsioonide arvu.
Siin on näide tükeldamise kasutamisest:
async function processInChunks(promises, chunkSize) {
const results = [];
for (let i = 0; i < promises.length; i += chunkSize) {
const chunk = promises.slice(i, i + chunkSize);
const chunkResults = await Promise.all(chunk);
results.push(...chunkResults);
}
return results;
}
// Example usage
const myPromises = [...Array(100)].map((_, i) => Promise.resolve(i)); //Loo 100 Promise'i
processInChunks(myPromises, 10) // Töötle 10 Promise'i korraga
.then(results => console.log('All promises resolved:', results));
Vigade sujuv käsitlemine
Õige veakäsitlus on Promise'idega töötamisel ülioluline. Kasutage try...catch plokke, et püüda kinni vigu, mis võivad tekkida asünkroonsete operatsioonide ajal. Kaaluge teekide nagu p-retry või retry kasutamist, et ebaõnnestunud operatsioone automaatselt uuesti proovida.
async function fetchDataWithRetry(url, retries = 3) {
try {
const response = await fetch(url);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return await response.json();
} catch (error) {
if (retries > 0) {
console.log(`Retrying in 1 second... (Retries left: ${retries})`);
await new Promise(resolve => setTimeout(resolve, 1000)); // Oota 1 sekund
return fetchDataWithRetry(url, retries - 1);
} else {
console.error('Max retries reached. Operation failed.');
throw error;
}
}
}
Async/Await kasutamine
async ja await pakuvad sünkroonsema välimusega viisi Promise'idega töötamiseks. Need võivad oluliselt parandada koodi loetavust ja hooldatavust.
Pidage meeles kasutada try...catch plokke await-avaldiste ümber, et käsitleda võimalikke vigu.
Tühistamine
Mõnes stsenaariumis võib teil tekkida vajadus tühistada ootel olevaid Promise'e, eriti pikaajaliste operatsioonide või kasutaja algatatud toimingute puhul. Saate kasutada AbortController API-t, et anda märku, et Promise tuleks tühistada.
const controller = new AbortController();
const signal = controller.signal;
async function fetchDataWithCancellation(url) {
try {
const response = await fetch(url, { signal });
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return await response.json();
} catch (error) {
if (error.name === 'AbortError') {
console.log('Fetch aborted');
} else {
console.error('Error fetching data:', error);
}
throw error;
}
}
fetchDataWithCancellation('https://api.example.com/data')
.then(data => console.log('Data received:', data))
.catch(error => console.error('Fetch failed:', error));
// Tühista toomise operatsioon 5 sekundi pärast
setTimeout(() => {
controller.abort();
}, 5000);
Kokkuvõte
JavaScripti Promise'i kombinaatorid on võimsad tööriistad töökindlate ja tõhusate asünkroonsete rakenduste loomiseks. Mõistes Promise.all, Promise.allSettled, Promise.race ja Promise.any nüansse, saate orkestreerida keerukaid asünkroonseid töövooge, käsitleda vigu sujuvalt ja optimeerida jõudlust. Globaalsete rakenduste arendamisel on ülioluline arvestada võrgu latentsuse, API päringute piirangute ja andmeallikate usaldusväärsusega. Rakendades selles artiklis käsitletud mustreid ja parimaid praktikaid, saate luua JavaScripti rakendusi, mis on nii jõudsad kui ka vastupidavad, pakkudes suurepärast kasutajakogemust kasutajatele üle kogu maailma.