JavaScript'те көп аракеттер асинхрондуу .
Мисалы, функцияны карап көрөлү loadScript(src)
:
@A@function loadScript(src) {
let script = document.createElement('script');
script.src = src;
document.head.append(script);
}@A@
Бул функция бетке жаңы скрипт жүктөйт. Конструкция документтин негизги бөлүгүнө кошулганда <script src="…">
, браузер скриптти жүктөп алып, аны аткарат.
Бул функцияны колдонуунун мисалы:
@A@// загрузит и выполнит скрипт
loadScript('/my/script.js');@A@
Мындай функциялар "асинхрондук" деп аталат, анткени аракет (скриптти жүктөө) азыр эмес, кийинчерээк бүтөт.
Чалуудан кийин кандайдыр бир код болсо loadScript(…)
, анда ал скрипттин жүктөлүшүн күтпөйт.
@A@loadScript('/my/script.js');
// код, написанный после вызова функции loadScript,
// не будет дожидаться полной загрузки скрипта
// ...@A@
Жаңы скрипт жүктөлгөндөн кийин аны колдонгубуз келет. Келгиле, ал биз аткаргыбыз келген жаңы функцияны жарыялады дейли.
Бирок бул функцияны кийин гана чакырсак loadScript(…)
, андан эч нерсе чыкпайт:
@A@loadScript('/my/script.js'); // в скрипте есть "function newFunction() {…}"
newFunction(); // такой функции не существует!@A@
Чынында эле, браузердин сценарийди жүктөөгө убактысы болгон эмес. Эми функция loadScript
жүктөө учуруна көз салууга мүмкүндүк бербейт. Скрипт жүктөлүп, андан кийин аткарылат. Бирок бул скрипттеги функцияларды жана өзгөрмөлөрдү колдонуу үчүн бул качан болоорун так билишибиз керек.
Функцияны скрипт жүктөлгөндө аны чакыруу callback
үчүн экинчи аргумент катары берели :loadScript
@A@function loadScript(src, callback) {
let script = document.createElement('script');
script.src = src;
script.onload = () => callback(script);
document.head.append(script);
}@A@
Окуя onload
макалада сүрөттөлгөн. Ресурстарды жүктөө: жүктөө жана ката , ал негизинен скрипт жүктөлгөндөн жана аткарылгандан кийин функцияны аткарат.
Эми, эгерде биз скрипттен функцияны чакыргыбыз келсе, аны кайра чакырууда аткарышыбыз керек:
@A@loadScript('/my/script.js', function() {
// эта функция вызовется после того, как загрузится скрипт
newFunction(); // теперь всё работает
...
});@A@
Мааниси мындай: экинчи аргумент – бул иш-аракет аяктагандан кийин аткарылуучу функция (көбүнчө анонимдүү).
Мисал катары функциялар китепканасы бар реалдуу сценарийди алалы:
@A@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(`Здорово, скрипт ${script.src} загрузился`);
alert( _ ); // функция, объявленная в загруженном скрипте
});@A@
Бул жазуу кайра чалууларды колдонуу менен асинхрондук программалоо деп аталат. Ар кандай асинхрондук операцияларды аткарган функцияларда аргумент берилет callback
- асинхрондук аракет аяктагандан кийин чакырыла турган функция.
Бизде да ушундай кылдык loadScript
, бирок бул, албетте, жалпы мамиле.
колбекте колбек
Кантип эки сценарийди биринин артынан бири жүктөйбүз: биринчиси, анан экинчиси?
Акылга келген биринчи нерсе, аны loadScript
кайра чалуу ичинде кайра чакыруу, мисалы:
@A@loadScript('/my/script.js', function(script) {
alert(`Здорово, скрипт ${script.src} загрузился, загрузим ещё один`);
loadScript('/my/script2.js', function(script) {
alert(`Здорово, второй скрипт загрузился`);
});
});@A@
Сырткы функция loadScript
аткарылганда, кайра чакыруунун ичиндегиси чакырылат.
Башка скрипт жүктөшүбүз керек болсочу?..
@A@loadScript('/my/script.js', function(script) {
loadScript('/my/script2.js', function(script) {
loadScript('/my/script3.js', function(script) {
// ...и так далее, пока все скрипты не будут загружены
});
})
});@A@
Биз кайра чалуунун ичиндеги ар бир жаңы аракетти чакырышыбыз керек. Бул параметр бизде бир же эки иш болгондо ылайыктуу, бирок көбүрөөк үчүн бул ыңгайлуу эмес. Биз жакын арада альтернативдүү ыкмаларды талкуулайбыз.
error
Жогорудагы мисалдарда биз каталар жөнүндө ойлонгон жокпуз. Эгер скрипт жүктөлбөй калсачы? Кайра чалуу мүмкүн болгон көйгөйлөргө жооп бере алышы керек.
loadScript
Төмөндө жүктөө каталарын көзөмөлдөй турган жакшыртылган версия :
@A@function loadScript(src, callback) {
let script = document.createElement('script');
script.src = src;
script.onload = () => callback(null, script);
script.onerror = () => callback(new Error(`Не удалось загрузить скрипт ${src}`));
document.head.append(script);
}@A@
Биз скрипттин callback(null, script)
ийгиликтүү жүктөлүшүнө жана callback(error)
жүктөлбөй калышына чакырабыз.
Тирүү мисал:
@A@loadScript('/my/script.js', function(error, script) {
if (error) {
// обрабатываем ошибку
} else {
// скрипт успешно загружен
}
});@A@
Дагы, биз колдонгон ыкма loadScript
да кеңири таралган жана "ката-биринчи кайра чалуу" деп аталат.
Эрежелер:
- Биринчи функция аргументи
callback
ката үчүн сакталган. Бул учурда, чакыруу төмөнкүдөй көрүнөт:callback(err)
. - Экинчи жана андан кийинки аргументтер аткаруунун натыйжалары үчүн. Бул учурда, чакыруу төмөнкүдөй көрүнөт:
callback(null, result1, result2…)
.
Ошол эле функция callback
ката жөнүндө кабарлоо үчүн да, натыйжаларды өткөрүү үчүн да колдонулат.
Чакыруулардынтозок пирамидасы
Бир караганда, бул асинхрондук кодду жазуунун жумушчу жолу. Бул чыныгы. Бир же эки чалуу үчүн баары жакшы көрүнөт.
Бирок биринин артынан бири аткарылышы керек болгон бир нече асинхрондук аракеттер үчүн код төмөнкүдөй көрүнөт:
@A@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 {
// ...и так далее, пока все скрипты не будут загружены (*)
}
});
}
})
}
});@A@
Жогорудагы мисалда:
- Биз жүктөп жатабыз
1.js
. Ката жок болсо, улантыңыз. - Биз жүктөп жатабыз
2.js
. Ката жок болсо, улантыңыз. - Биз жүктөп жатабыз
3.js
. Ката жок болсо, улантыңыз. Жана башкалар(*)
.
Канчалык көп уяланган чалуулар болсо, биздин код ошончолук уяланган болот, аны сактоо кыйын, айрыкча анын ордуна ...
бизде башка чалуу тизмектерин, шарттарды ж.б. камтыган код болсо.
Бул кээде "кайра чалуу тозогу" же "кайра чалуу тозогу пирамидасы" деп аталат.
Уюшкан чалуулар пирамидасы ар бир асинхрондук аракет менен оңго өсөт. Натыйжада, сен өзүң кайда экенин түшүнбөй каласың.
Код жазууга мындай мамиле жакпайт.
Ар бир аракетти өзүнчө функцияга бөлүп, бул маселени чечүүгө аракет кылсак болот, мисалы:
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 {
// ...и так далее, пока все скрипты не будут загружены (*)
}
};
Байкадыңызбы? Бул код баарын бирдей кылат, бирок уя жок, анткени бардык аракеттер өзүнчө функцияларга жайгаштырылат.
Код толугу менен иштеп жатат, бирок ал үзүлүп кеткен окшойт. Окуу кыйын, сиз муну байкагандырсыз. Сиз аны окууга аракет кылып жатканда коддун бөлүктөрүнүн ортосунда секирип өтүшүңүз керек. Бул ыңгайсыз, айрыкча, окурман код менен тааныш эмес жана андан кийин эмне болорун билбесе.
Мындан тышкары, бардык функциялар step*
бир жолу колдонулуучу жана "чалуулардын тозок пирамидасынан" кутулуу үчүн гана түзүлгөн. Аларды эч ким башка жерде кайра колдонбойт. Ошентип, биз, башка нерселер менен бирге, аттар мейкиндигин таштанды.
Биз жакшыраак жолду табышыбыз керек.
Бактыга жараша, мындай ыкмалар бар. Эң жакшы нерселердин бири - кийинки бөлүмдө камтылган убадаларды колдонуу.