Callbacklar, promiselar va boshqa abstrakt tushunchalardan foydalanishni koârsatish uchun biz baâzi brauzer metodlarini ishlatamiz: xususan, skriptlarni yuklash va oddiy hujjat manipulatsiyalari.
Agar bu metodlar bilan tanish boâlmasangiz va misollarda ulardan foydalanish chalkashlik tugâdirsa, darslikning keyingi qismidan bir nechta boblarni oâqishni xohlashingiz mumkin.
Biroq, baribir narsalarni aniq qilishga harakat qilamiz. Brauzer nuqtai nazaridan haqiqatan ham murakkab narsa boâlmaydi.
JavaScript host muhitlari tomonidan asinxron harakatlarni rejalashtirish imkonini beruvchi koâplab funktsiyalar taqdim etiladi. Boshqacha qilib aytganda, biz hozir boshlagan, lekin keyinroq tugaydigan harakatlar.
Masalan, bunday funktsiyalardan biri setTimeout funktsiyasidir.
Asinxron harakatlarning boshqa real dunyo misollari mavjud, masalan skriptlar va modullarni yuklash (biz ularni keyingi boblarda koârib chiqamiz).
Berilgan src bilan skriptni yuklaydigan loadScript(src) funktsiyasiga qarang:
function loadScript(src) {
// <script> tegini yaratadi va uni sahifaga qo'shadi
// bu berilgan src bilan skriptni yuklashni boshlaydi va tugagach ishga tushiradi
let script = document.createElement('script');
script.src = src;
document.head.append(script);
}
U hujjatga berilgan src bilan yangi, dinamik yaratilgan <script src="â¦"> tegini kiritadi. Brauzer uni avtomatik ravishda yuklashni boshlaydi va tugagach bajaradi.
Biz bu funktsiyani shunday ishlatishimiz mumkin:
// berilgan yo'ldagi skriptni yuklash va bajarish
loadScript('/my/script.js');
Skript âasinxronâ bajariladi, chunki u hozir yuklashni boshlaydi, lekin funktsiya tugagandan keyin ishga tushadi.
Agar loadScript(â¦) ostida biron kod boâlsa, u skript yuklash tugashini kutmaydi.
loadScript('/my/script.js');
// loadScript ostidagi kod
// skript yuklash tugashini kutmaydi
// ...
Aytaylik, yangi skriptni u yuklanishi bilanoq ishlatishimiz kerak. U yangi funktsiyalarni eâlon qiladi va biz ularni ishga tushirishni xohlaymiz.
Lekin agar buni loadScript(â¦) chaqiruvidan keyin darhol qilsak, bu ishlamaydi:
loadScript('/my/script.js'); // skriptda "function newFunction() {â¦}" mavjud
newFunction(); // bunday funktsiya yo'q!
Tabiiyki, brauzer skriptni yuklashga vaqt topmagan boâlishi mumkin. Hozircha loadScript funktsiyasi yuklash tugashini kuzatish usulini taqdim etmaydi. Skript yuklanadi va oxir-oqibat ishga tushadi, hammasi shu. Lekin biz bu qachon sodir boâlishini bilishni xohlaymiz, oâsha skriptdagi yangi funktsiyalar va oâzgaruvchilardan foydalanish uchun.
Skript yuklanganida bajarilishi kerak boâlgan ikkinchi argument sifatida loadScript ga callback funktsiyasini qoâshaylik:
function loadScript(src, callback) {
let script = document.createElement('script');
script.src = src;
script.onload = () => callback(script);
document.head.append(script);
}
onload hodisasi Resurslarni yuklash: onload va onerror maqolasida tasvirlangan, u asosan skript yuklangandan va bajarilagandan keyin funktsiyani bajaradi.
Endi agar skriptdan yangi funktsiyalarni chaqirishni xohlasak, buni callbackda yozishimiz kerak:
loadScript('/my/script.js', function() {
// callback skript yuklanganidan keyin ishga tushadi
newFunction(); // endi bu ishlaydi
...
});
Bu gâoya: ikkinchi argument funktsiya (odatda anonim) boâlib, harakat tugallanganda ishga tushadi.
Mana haqiqiy skript bilan ishlaydigan misol:
function loadScript(src, callback) {
let script = document.createElement('script');
script.src = src;
script.onload = () => callback(script);
document.head.append(script);
}
loadScript('https://cdnjs.cloudflare.com/ajax/libs/lodash.js/3.2.0/lodash.js', script => {
alert(`Ajoyib, ${script.src} skripti yuklandi`);
alert( _ ); // _ yuklangan skriptda e'lon qilingan funktsiya
});
Bu asinxron dasturlashning âcallback-asosidagiâ uslubi deb ataladi. Biror narsani asinxron bajaradigan funktsiya callback argumentini taqdim etishi kerak, bu yerda biz tugagandan keyin ishga tushadigan funktsiyani joylashtiramiz.
Bu yerda biz buni loadScript da qildik, lekin albatta bu umumiy yondashuv.
Callback ichida callback
Ikki skriptni ketma-ket qanday yuklashimiz mumkin: birinchi, keyin ikkinchisini undan keyin?
Tabiiy yechim ikkinchi loadScript chaqiruvini callback ichiga qoâyish boâladi:
loadScript('/my/script.js', function(script) {
alert(`Ajoyib, ${script.src} yuklandi, keling yana birini yuklaylik`);
loadScript('/my/script2.js', function(script) {
alert(`Ajoyib, ikkinchi skript yuklandi`);
});
});
Tashqi loadScript tugagandan keyin, callback ichki birini ishga tushiradi.
Agar yana bitta skript kerak boâlsaâ¦?
loadScript('/my/script.js', function(script) {
loadScript('/my/script2.js', function(script) {
loadScript('/my/script3.js', function(script) {
// ...barcha skriptlar yuklangandan keyin davom etish
});
});
});
Shunday qilib, har bir yangi harakat callback ichida. Bu kam harakatlar uchun yaxshi, lekin koâp uchun yaxshi emas, shuning uchun tez orada boshqa variantlarni koâramiz.
Xatolarni hal qilish
Yuqoridagi misollarda biz xatolarni hisobga olmadik. Agar skript yuklash muvaffaqiyatsiz boâlsa-chi? Bizning callbackimiz bunga javob bera olishi kerak.
Mana yuklash xatolarini kuzatadigan loadScript ning yaxshilangan versiyasi:
function loadScript(src, callback) {
let script = document.createElement('script');
script.src = src;
script.onload = () => callback(null, script);
script.onerror = () => callback(new Error(`${src} uchun skript yuklash xatosi`));
document.head.append(script);
}
U muvaffaqiyatli yuklash uchun callback(null, script) ni, aks holda callback(error) ni chaqiradi.
Foydalanish:
loadScript('/my/script.js', function(error, script) {
if (error) {
// xatoni hal qilish
} else {
// skript muvaffaqiyatli yuklandi
}
});
Yana bir bor, loadScript uchun ishlatgan retsept aslida juda keng tarqalgan. Bu âerror-first callbackâ uslubi deb ataladi.
Konventsiya:
callbackning birinchi argumenti agar xato yuz bersa, unga ajratilgan. Keyincallback(err)chaqiriladi.- Ikkinchi argument (va kerak boâlsa keyingilari) muvaffaqiyatli natija uchun. Keyin
callback(null, result1, result2â¦)chaqiriladi.
Shunday qilib bitta callback funktsiyasi ham xatolarni bildirish, ham natijalarni uzatish uchun ishlatiladi.
Halokat piramidasi
Birinchi qarashda, bu asinxron kodlash uchun mumkin boâlgan yondashuv koârinadi. Va haqiqatan ham shunday. Bir yoki ehtimol ikki ichki chaqiruv uchun yaxshi koârinadi.
Lekin bir-biridan keyin keladigan koâplab asinxron harakatlar uchun bizda bunday kod boâladi:
loadScript('1.js', function(error, script) {
if (error) {
handleError(error);
} else {
// ...
loadScript('2.js', function(error, script) {
if (error) {
handleError(error);
} else {
// ...
loadScript('3.js', function(error, script) {
if (error) {
handleError(error);
} else {
// ...barcha skriptlar yuklangandan keyin davom etish (*)
}
});
}
});
}
});
Yuqoridagi kodda:
- Biz
1.jsni yuklaymiz, agar xato boâlmasa⦠- Biz
2.jsni yuklaymiz, agar xato boâlmasa⦠- Biz
3.jsni yuklaymiz, agar xato boâlmasa â boshqa narsa qilamiz(*).
Chaqiruvlar koâproq ichki boâlgan sari, kod chuqurroq va tobora boshqarish qiyinroq boâladi, ayniqsa agar bizda ... oârniga koâproq sikl, shartli ifodalar va boshqalarni oâz ichiga olgan haqiqiy kod boâlsa.
Bu baâzan âcallback doâzaxâ yoki âhalokat piramidasiâ deb ataladi.
Ichma-ich chaqiruvlarning âpiramidasiâ har bir asinxron harakat bilan oângga oâsib boradi. Tez orada u nazoratdan chiqadi.
Shuning uchun bunday kodlash usuli unchalik yaxshi emas.
Har bir harakatni mustaqil funktsiya qilish orqali muammoni engillashtirishga harakat qilishimiz mumkin:
loadScript('1.js', step1);
function step1(error, script) {
if (error) {
handleError(error);
} else {
// ...
loadScript('2.js', step2);
}
}
function step2(error, script) {
if (error) {
handleError(error);
} else {
// ...
loadScript('3.js', step3);
}
}
function step3(error, script) {
if (error) {
handleError(error);
} else {
// ...barcha skriptlar yuklangandan keyin davom etish (*)
}
}
Koâryapsizmi? Bu bir xil narsani qiladi va endi chuqur ichki chaqiruvlar yoâq, chunki biz har bir harakatni alohida yuqori darajali funktsiya qildik.
Bu ishlaydi, lekin kod yirtilib ketgan elektron jadval kabi koârinadi. Uni oâqish qiyin va siz ehtimol fark qilgansiz, oâqish paytida qismlar orasida koâz bilan sakrash kerak. Bu noqulay, ayniqsa oâquvchi kod bilan tanish boâlmasa va qayerga sakrashni bilmasa.
Bundan tashqari, step* deb nomlangan funktsiyalarning barchasi faqat bir marta ishlatiladi, ular faqat "halokat piramidasi"dan qochish uchun yaratilgan. Ularni harakat zanjiridan tashqarida hech kim qayta ishlatmaydi. Shuning uchun bu yerda biroz namespace ifloslanishi bor.
Bizda yaxshiroq narsa boâlishini xohlaymiz.
Baxtga qarshi, bunday piramidalardan qochishning boshqa usullari ham bor. Eng yaxshi usullardan biri keyingi bobda tasvirlangan "promiselar"dan foydalanishdir.
Izohlar
<code>yorlig'ini ishlating, bir nechta satrlar uchun - ularni<pre>yorlig'i bilan o'rab qo'ying, 10 satrdan ortiq bo'lsa - sandbox (plnkr, jsbin, codepenâ¦)