وعده ها ، دستگیره ها و زنجیرها پایه های جاوا اسکریپت مدرن هستند ، اما می توانند مشکل باشند. در اینجا چهار تله مشترک برای تماشای آن آورده شده است.
من قبلاً پوشش داده ام مبانی وعده های JavaScript قول می دهد و نحوه استفاده از کلمات کلیدی Angync/Await برای ساده کردن کد ناهمزمان موجود. این مقاله نگاهی پیشرفته تر به جاوا اسکریپت است. ما چهار روش مشترک را کشف خواهیم کرد که وعده های سفر به توسعه دهندگان و ترفندهایی را برای حل آنها انجام می دهیم.
gotcha #1: وعده های بازگرداندن وعده های بازگشت
اگر اطلاعات را از سپس
یا گرفتن
بازگردانید ، اگر این وعده از قبل نباشد ، همیشه در یک قول پیچیده می شود. بنابراین ، شما هرگز نیازی به نوشتن کد مانند این دارید:
firstAjaxCall.then(() => {
return new Promise((resolve, reject) => {
nextAjaxCall().then(() => resolve());
});
});
از آنجا که nextajaxcall
همچنین یک وعده را برمی گرداند ، می توانید به جای آن این کار را انجام دهید:
firstAjaxCall.then(() => {
return nextAjaxCall();
});
علاوه بر این ، اگر یک مقدار ساده (غیر تبلیغی) را برمی گردانید ، کنترل کننده قول حل شده به آن مقدار را برمی گرداند ، بنابراین می توانید با سپس در نتایج تماس بگیرید:
firstAjaxCall.then((response) => {
return response.importantField
}).then((resolvedValue) => {
// resolvedValue is the value of response.importantField returned above
console.log(resolvedValue);
});
این همه بسیار راحت است ، اما اگر وضعیت یک مقدار دریافتی را نمی دانید؟
ترفند شماره ۱: برای حل مقادیر ورودی از وعده استفاده کنید.
اگر مطمئن نیستید که آیا مقدار ورودی شما یک وعده است ، می توانید به سادگی از روش استاتیک promise.resolve ()
استفاده کنید. به عنوان مثال ، اگر متغیری را دریافت می کنید که ممکن است یا ممکن است وعده ای نباشد ، کافی است آن را به عنوان یک آرگومان به promise.resolve
منتقل کنید. اگر متغیر یک وعده باشد ، روش قول را برمی گرداند. اگر متغیر یک مقدار باشد ، روش قول حل شده به مقدار را برمی گرداند:
let processInput = (maybePromise) => {
let definitelyPromise = Promise.resolve(maybePromise);
definitelyPromise.then(doSomeWork);
};
gotcha #2:. سپس همیشه یک عملکرد را انجام می دهد
شما احتمالاً کد وعده (و احتمالاً نوشته شده) را مشاهده کرده اید که چیزی شبیه به این است:
let getAllArticles = () => {
return someAjax.get('/articles');
};
let getArticleById = (id) => {
return someAjax.get(`/articles/${id}`);
};
getAllArticles().then(getArticleById(2));
هدف از کد فوق این است که ابتدا تمام مقالات را بدست آوریم و پس از آن ، پس از اتمام ، مقاله
را با شناسه ۲ دریافت کنید. در حالی که ممکن است ما یک اجرای پی در پی را داشته باشیم ، آنچه اتفاق می افتد این است که این دو وعده در همان زمان آغاز می شود ، این بدان معناست که آنها می توانند به هر ترتیب تکمیل شوند.
مسئله اینجاست که ما نتوانستیم به یکی از قوانین اساسی جاوا اسکریپت رعایت کنیم: این آرگومان برای توابع همیشه قبل از انتقال به عملکرد ارزیابی می شود. .Then
عملکردی دریافت نمی کند. این مقدار بازگشت getArticleByid
را دریافت می کند. این امر به این دلیل است که ما بلافاصله با عملگر پرانتز تماس می گیریم.
چند روش برای رفع این مشکل وجود دارد.
ترفند شماره ۱: تماس را در یک تابع فلش بسته بندی کنید
اگر می خواستید دو عملکرد خود را به صورت متوالی پردازش کنید ، می توانید چنین کاری انجام دهید:
// A little arrow function is all you need
getAllArticles().then(() => getArticleById(2));
با بسته بندی تماس به getArticleByid
در یک عملکرد فلش ، ما . then
را با عملکردی که می تواند هنگام getAllArticles ()
حل کرده باشد ، ارائه می دهیم.
ترفند شماره ۲: عبور در توابع نامگذاری شده به
شما همیشه لازم نیست از توابع ناشناس درون خطی به عنوان آرگومان استفاده کنید. سپس
. به راحتی می توانید یک تابع را به یک متغیر اختصاص داده و مرجع آن عملکرد را به .Then
منتقل کنید.
// function definitions from Gotcha #2
let getArticle2 = () => {
return getArticleById(2);
};
getAllArticles().then(getArticle2);
getAllArticles().then(getArticle2);
در این حالت ، ما فقط در حال ارجاع به عملکرد هستیم و آن را صدا نمی کنیم.
ترفند شماره ۳: از async/await
استفاده کنید
راه دیگر برای روشن تر کردن ترتیب رویدادها استفاده از async/await
کلمات کلیدی:
async function getSequentially() {
const allArticles = await getAllArticles(); // Wait for first call
const specificArticle = await getArticleById(2); // Then wait for second
// ... use specificArticle
}
اکنون ، این واقعیت که ما دو مرحله را انجام می دهیم ، هر یک از آنها به دنبال دیگری ، صریح و آشکار است. ما تا پایان هر دو به اعدام ادامه نمی دهیم. این یک تصویر عالی از وضوح در انتظار
هنگام مصرف وعده ها است.
gotcha #3: آرگومان های غیر عملکردی.
حالا بیایید GOTCHA شماره ۲ را بگیریم و کمی پردازش اضافی را به انتهای زنجیره اضافه کنیم:
let getAllArticles = () => {
return someAjax.get('/articles');
};
let getArticleById = (id) => {
return someAjax.get(`/articles/${id}`);
};
getAllArticles().then(getArticleById(2)).then((article2) => {
// Do something with article2
});
ما از قبل می دانیم که این زنجیره به طور متوالی همانطور که می خواهیم اجرا نمی شود ، اما اکنون ما برخی از رفتارهای عجیب و غریب را در Promiseland کشف کرده ایم. به نظر شما مقدار Article2
در آخرین . سپس
چیست؟
از آنجا که ما تابعی را به اولین آرگومان منتقل نمی کنیم. سپس
، JavaScript با مقدار حل شده خود به قول اولیه می رود ، بنابراین مقدار Article2
هر چیزی است که getAllArticles ()
برطرف شده است. اگر زنجیره ای طولانی از روشهای . then
دارید و برخی از دستگیرندگان شما از روشهای قبلی . سپس
مقادیر دریافت می کنند ، اطمینان حاصل کنید که در واقع توابع را به .Then
منتقل می کنید.
ترفند شماره ۱: عبور در توابع نامگذاری شده با پارامترهای رسمی
یکی از راه های رسیدگی به این امر ، عبور در توابع نامگذاری شده است که یک پارامتر رسمی واحد را تعریف می کنند (یعنی یک آرگومان را بگیرید). این به ما امکان می دهد تا برخی از کارکردهای عمومی را ایجاد کنیم که بتوانیم در زنجیره ای از روشهای . then
یا خارج از زنجیره استفاده کنیم.
بیایید بگوییم که ما یک تابع داریم ، getFirStarticle
، این باعث می شود یک تماس API برای دریافت جدیدترین مقاله
در یک مجموعه باشد و به یک مقاله
با خصوصیاتی مانند شناسه ، عنوان و تاریخ انتشار برطرف شود. سپس بگوییم ما یک عملکرد دیگر داریم ، getCommentsforarticleId
، که شناسه مقاله را می گیرد و یک تماس API را برای دریافت تمام نظرات مرتبط با آن مقاله انجام می دهد.
اکنون ، تنها چیزی که ما باید دو کارکرد را به آن متصل کنیم این است که از مقدار وضوح عملکرد اول (یک مقاله
) به مقدار آرگومان مورد انتظار عملکرد دوم (شناسه مقاله) بدست آوریم. ما می توانیم از یک تابع درون خطی ناشناس برای این منظور استفاده کنیم:
getFirstArticle().then((article) => {
return getCommentsForArticleId(article.id);
});
یا ، ما می توانیم یک عملکرد ساده ایجاد کنیم که یک مقاله را می گیرد ، شناسه را برمی گرداند و همه چیز را به همراه .Then
زنجیره می دهد:
let extractId = (article) => article.id;
getFirstArticle().then(extractId).then(getCommentsForArticleId);
این راه حل دوم تا حدودی مقدار وضوح هر عملکرد را مبهم می کند ، زیرا آنها به صورت خطی تعریف نشده اند. اما ، از طرف دیگر ، برخی از کارکردهای انعطاف پذیر را ایجاد می کند که احتمالاً می توانیم از آن استفاده کنیم. توجه داشته باشید ، همچنین ، ما از آنچه از اولین GOTCHA آموخته ایم استفاده می کنیم: اگرچه ExtractId
وعده ای را برنمی گرداند ، . سپس
مقدار بازگشت خود را به صورت وعده ای پیچیده می کند ، که به ما اجازه می دهد تا دوباره با . then
تماس بگیریم.
ترفند شماره ۲: از async/await
استفاده کنید
یک بار دیگر ، async/await
می تواند با آشکارتر کردن چیزها به نجات بیاید:
async function getArticleAndComments() {
const article = await getFirstArticle();
const comments = await getCommentsForArticleId(article.id); // Extract ID directly
// ... use comments
}
در اینجا ، ما به سادگی منتظر هستیم تا getFirStarticle ()
به پایان برسد ، سپس از مقاله برای دریافت شناسه استفاده کنید. ما می توانیم این کار را انجام دهیم زیرا با اطمینان می دانیم که مقاله با عملیات اساسی حل شده است.
gotcha #4: هنگامی که async/انتظار همزمانی شما را خراب می کند
بیایید بگوییم که می خواهید چندین عمل ناهمزمان را به طور همزمان شروع کنید ، بنابراین آنها را در یک حلقه قرار می دهید و از در انتظار
استفاده می کنید:
// (Bad practice below!)
async function getMultipleUsersSequentially(userIds) {
const users = [];
const startTime = Date.now();
for (const id of userIds) {
// await pauses the *entire loop* for each fetch
const user = await fetchUserDataPromise(id);
users.push(user);
}
const endTime = Date.now();
console.log(`Sequential fetch took ${endTime - startTime}ms`);
return users;
}
// If each fetch takes 1.5s, 3 fetches would take ~4.5s total.
در این مثال ، آنچه ما می خواهیم ارسال همه این fetchuserdatapromise ()
با هم است. اما آنچه ما دریافت می کنیم است ، هر یک از آنها به صورت متوالی اتفاق می افتد ، به این معنی که حلقه منتظر است تا قبل از ادامه به مرحله بعدی تکمیل شود.
ترفند شماره ۱: از وعده استفاده کنید.
حل این یکی با promise.all
ساده است:
// (Requests happen concurrently)
async function getMultipleUsersConcurrently(userIds) {
console.log("Starting concurrent fetch...");
const startTime = Date.now();
const promises = userIds.map(id => fetchUserDataPromise(id));
const users = await Promise.all(promises);
const endTime = Date.now();
console.log(`Concurrent fetch took ${endTime - startTime}ms`);
return users;
}
// If each fetch takes 1.5s, 3 concurrent fetches would take ~1.5s total (plus a tiny overhead).
Promise.all
می گوید تمام وعده های
را در آرایه بگیرید و یکباره آنها را شروع کنید ، سپس صبر کنید تا همه آنها قبل از ادامه کار تمام شوند. در این مورد استفاده ، وعده ها رویکرد ساده تر از async/await
هستند. (اما توجه داشته باشید که ما هنوز از در انتظار
برای انتظار prome.all
استفاده می کنیم.)
نتیجه گیری
اگرچه ما اغلب می توانیم از async/await
برای حل مسائل در وعده ها استفاده کنیم ، اما درک وعده های خود به منظور درک واقعی آنچه که async/انتظار
کلمات کلیدی انجام می دهند ، بسیار مهم است. Gotchas در نظر گرفته شده است تا به شما کمک کند تا بهتر بفهمید که چگونه وعده داده می شود و چگونه می توانید از آنها به طور مؤثر در کد استفاده کنید.
پست های مرتبط
وعده های جاوا اسکریپت: ۴ گچ و نحوه جلوگیری از آنها
وعده های جاوا اسکریپت: ۴ گچ و نحوه جلوگیری از آنها
وعده های جاوا اسکریپت: ۴ گچ و نحوه جلوگیری از آنها