Дарси 16 · Async ва API
Ҳолатҳои боркунӣ, хато, debounce
Шумо аллакай маълумотро аз сервери воқеӣ гирифта метавонед. Аммо дар сайти воқеӣ интернет гоҳо суст аст, сервер — дастнорас, корбар бошад — бетоқат: вай дар сатри ҷустуҷӯ тезтар менависад, нисбат ба он ки ҷавобҳо меоянд. Имрӯз мо ба барнома меомӯзонем, ки дар ҳар кадом аз ин вазъиятҳо шоиста рафтор кунад: боркуниро нишон диҳад, хаторо шарҳ диҳад, серверро бо дархостҳо пур накунад ва нолозимро бекор кунад.
Чор ҳолати экранро фарқ кардан: боркунӣ, хато, холӣ, маълумот — ва ҳар якро кашидан
response.ok-ро санҷидан ва «сервер бо хато ҷавоб дод»-ро бо «шабака нест» омехта накардан
Skeleton сохтан — ҷойгузинҳои хокистарӣ дар ҷои мазмуни ояндаи — бо CSS-и пок
debounce-ро аз сифр навиштан ва шарҳ додан, ки он тавассути closure ва таймер чӣ тавр кор мекунад
Дархостро тавассути AbortController бекор кардан ва ҷавобҳои кӯҳнашударо нишон надодан
Тағйиротеро, ки агенти ЗС (Cursor) якбора ба якчанд файл ворид мекунад, хондан ва санҷидан
Бо ин саҳифа чӣ тавр кор кардан лозим
Коди дар заминаи торик бо тугмаи ▶ Иҷро кардан зинда аст: онро тағйир диҳед ва натиҷаро дар консол бинед. Дар ин дарс «сервер» дар мисолҳо атайин суст аст ва рост дар код сохта шудааст (тавассути Promise ва setTimeout), барои ҳамин натиҷа ҳамеша якхела аст ва аз интернети шумо вобаста нест; баъд аз иҷро як сония интизор шавед — сатрҳо дар консол ба навбат пайдо мешаванд. Мисолҳо бо API-и воқеӣ низ ҳастанд, аммо вазифаҳои санҷидашаванда бе шабака кор мекунанд. Дар ҷое ки равзанаи пешнамоиш ҳаст — ин саҳифаи хурд аст: дар тарафи чап код, дар тарафи рост натиҷа. Ишораҳо зери қулфак 🔒 пинҳонанд: аввал худатон кӯшиш кунед.
01 Чор ҳолати экран
Тасаввур кунед, ки шумо барномаи таксиро кушодед ва «Фармоиш додан»-ро пахш кардед. Чӣ мебинед? Чархаки «мошин меҷӯем». Агар дар наздикӣ мошин набошад — паёми «Мошини холӣ нест, баъдтар кӯшиш кунед». Агар интернет гум шавад — «Пайвастшавӣ нест» ва тугмаи «Такрор кардан». Ва танҳо агар ҳама чиз хуб бошад — корти ронанда. Як экран, чор тасвири гуногун — вобаста ба он ки ҳоло бо маълумот чӣ мегузарад.
Навомӯзон одатан танҳо тасвири охиринро мекашанд — «маълумот омад». Се тасвири дигарро фаромӯш мекунанд. Дар натиҷа бо интернети суст корбар ба саҳифаи холӣ менигарад ва гумон мекунад, ки сайт вайрон шудааст. Касбӣ ҳар чорро фикр мекунад:
loading дархост рафт, ҷавоб ҳанӯз нест чархак ё skeleton
error чизе хато рафт матни фаҳмо + «Такрор кардан»
empty ҷавоб омад, аммо холӣ аст «Ничего не найдено»
success ҷавоб омад, маълумот ҳаст рӯйхат, кортҳо, ҷадвал
Чор ҳолати экране, ки маълумоти серверро нишон медиҳад. Плюс «idle» — лаҳзаи оғоз, вақте ҳанӯз чизе дархост нашудааст
Қиёс аз ҳаёт
Шумо ба чойхона омада, курутоб фармоиш додед. Пешхизмати хуб хомӯшона намеистад: «Тайёр мекунем, тақрибан даҳ дақиқа» (loading); «Курутоб тамом шуд» (error — фармоиш додан мумкин нест); «Мутаассифона, чунин хӯрок дар меню нест» (empty — ҷустем, аммо наёфтем); ва ниҳоят — табақро меорад (success). Пешхизмати бад оддӣ ғайб мезанад, шумо бошед менишинед ва тахмин мекунед: фармоиш қабул шуд ё не?
Ҳолатро дар куҷо нигоҳ доштан
Аз дарси ҳолат (дарси 11) шумо қоидаи асосиро медонед: маълумот ҳаст — функсияе ҳаст, ки аз рӯи он экранро мекашад . Дар ин ҷо ҳам ҳамин тавр. Як объекти state бо се майдон ва як функсияи view (ё render) месозем, ки қарор медиҳад, чиро нишон диҳад.
Ҳолат ва функсияе, ки аз рӯи он мекашад (бо матн) · Ctrl + Enter ↺ Аз нав ▶ Иҷро кардан
// status: "idle" | "loading" | "success" | "error"
function view (state) {
if (state.status === "idle" ) return "Введите название блюда" ;
if (state.status === "loading" ) return "Загрузка…" ;
if (state.status === "error" ) return `Ошибка: ${state.error}` ;
// минбаъд status === "success"
if (state.data.length === 0 ) return "Ничего не найдено" ;
return `Нашли: ${state.data.join(", ")}` ;
}
console.log (view ({ status: "idle" , data: [], error: null }));
console.log (view ({ status: "loading" , data: [], error: null }));
console.log (view ({ status: "error" , data: [], error: "Нет сети" }));
console.log (view ({ status: "success" , data: [], error: null }));
console.log (view ({ status: "success" , data: ["Курутоб" ], error: null }));
Диққат диҳед: «empty» — ин статуси алоҳидаи сервер нест , балки ҳолати хоси муваффақият аст: дархост гузашт, аммо рӯйхат холӣ баромад. Хоҳед — онро бо статуси алоҳидаи "empty" ҷудо кунед (дар тамға қулайтар мешавад), хоҳед — data.length-ро санҷед. Асосӣ — корбар матни гуногунро бинад.
Тартиби санҷишҳо муҳим аст
Хатои маъмултарин — аввал «холӣ?»-ро санҷидан. То даме ки маълумот бор мешавад, рӯйхат ҳам холӣ аст ва корбар ба ҷои «Загрузка…» «Ничего не найдено»-ро мебинад. Аввал мепурсем: «бо дархост чӣ шуд?» (loading, error), ва танҳо баъд — «дар дохили ҷавоб чӣ ҳаст?» (холӣ ё не).
Рӯйхати холӣ якум
Ҳангоми боркунӣ — «Ничего не найдено». Корбар гумон мекунад, ки хӯрок нест.
Таҳрир кардан мумкин аст · Ctrl + Enter ↺ Аз нав ▶ Иҷро кардан
function view (state) {
if (state.data.length === 0 ) return "Ничего не найдено" ;
if (state.status === "loading" ) return "Загрузка…" ;
return state.data.join (", " );
}
console.log (view ({ status: "loading" , data: [] }));
// → Ничего не найдено 😕
Аввал статус
Аввал loading ва error, баъд бо мазмун сару кор мегирем.
Таҳрир кардан мумкин аст · Ctrl + Enter ↺ Аз нав ▶ Иҷро кардан
function view (state) {
if (state.status === "loading" ) return "Загрузка…" ;
if (state.data.length === 0 ) return "Ничего не найдено" ;
return state.data.join (", " );
}
console.log (view ({ status: "loading" , data: [] }));
// → Загрузка… ✓
Хатои маъмул
Ба ҷои чор ҳолат — як булии isLoading ва «баъд мебинем». Он гоҳ ба осонӣ омезишҳои имконнопазир ҳосил мешаванд: isLoading: true ва дар ҳамон вақт error: "Нет сети". Як status бо қиматҳои фаҳмо чунин чизро намепазирад: дар ҳар лаҳза вай ба якто аз онҳо баробар аст.
Гузариш байни ҳолатҳо
Ҳолатҳо бо сенарияи оддӣ ҷойи ҳамдигарро мегиранд. Онро дар хотир доред — вай дар ҳар барномае, ки менависед, хоҳад буд:
Корбар коре кард (саҳифаро кушод, «Ёфтан»-ро пахш кард) → status: "loading"-ро мемонем ва экранро аз нав мекашем.
Сервер ҷавоб дод → status: "success"-ро (ё "empty") мемонем ва маълумотро мегузорем.
Чизе вайрон шуд → status: "error"-ро мемонем ва матни фаҳмои хаторо мегузорем.
Корбар «Такрор кардан»-ро пахш кард → боз қадами 1.
Маслиҳат
Ҳангоми дархости нав судманд аст, ки маълумоти кӯҳнаро то боркунӣ нест накунед , ҳангоми хато — рӯйхати аллакай нишондодаро гум накунед. Он гоҳ болои рӯйхати пештара «Навсозӣ муяссар нашуд»-ро кашидан мумкин аст, на ин ки корбарро бо экрани холӣ гузоштан. Имрӯз мо дар ҷое ки қулай аст, ҳамин тавр мекунем.
Муфассалтар хонед: MDN: aria-busy — чӣ тавр дар бораи боркунӣ ба технологияҳои ёрирасон хабар додан · learn.javascript.ru: Fetch
Амал: мустаҳкам мекунем
Ҳоло бе шабака ва бе таймерҳо: бо ҳолатҳо фикр карданро меомӯзем.
ВАЗИФАИ 1.1 Экран чӣ нишон медиҳад осон
Чопро пешгӯӣ кунед , бе иҷрои код: label барои ҳар кадом аз чор ҳолат чӣ чоп мекунад? Сатрҳоро ба prediction ворид кунед ва иҷро намоед. Ба ҳолати дуюм диққат диҳед: статуси «success», аммо маълумот нест.
Таҳрир кардан мумкин аст · Ctrl + Enter ↺ Аз нав ▶ Иҷро кардан
const states = [
{ status: "loading" },
{ status: "success" , data: [] },
{ status: "success" , data: ["Курутоб" , "Нон" ] },
{ status: "error" , error: "Нет сети" },
];
function label (s) {
if (s.status === "loading" ) return "Загрузка…" ;
if (s.status === "error" ) return `Ошибка: ${s.error}` ;
if (s.data.length === 0 ) return "Ничего не найдено" ;
return `Найдено: ${s.data.length}` ;
}
const prediction = ["" , "" , "" , "" ]; // пешгӯии шумо: чор ёддошт бо тартиб
const actual = states.map (label);
console.log (actual.join ("|" ) === prediction.join ("|" ) ? "Верно!" : "Не угадали, подумайте ещё" );
Натиҷаи интизоршаванда Верно!
Ишора 1 Ҳолатҳоро аз боло ба поён тай кунед ва барои ҳар кадом дар label аввалин санҷиши кордодаро ёбед.
Ишора 2 Ҳолати аввал майдони data надорад, аммо ба санҷиши s.data.length кор намерасад: функсия аллакай дар return-и аввал баромад.
○ Ҳалшуда қайд кардан 💬 Дар бораи ин вазифа пурсидан
ВАЗИФАИ 1.2 Матн барои ҳар ҳолат осон
Функсияи view(state)-ро нависед, ки матнро барои экрани ҷустуҷӯи хӯрокҳо бармегардонад: барои idle — Введите запрос; барои loading — Загрузка…; барои error — Ошибка: ва матни хато; агар маълумот набошад — Ничего не найдено по запросу «…» (бо дархост аз state.query); вагарна — Блюда: ва номҳо бо вергул.
Таҳрир кардан мумкин аст · Ctrl + Enter ↺ Аз нав ▶ Иҷро кардан
// function view(state) { ... }
console.log (view ({ status: "idle" , query: "" , data: [], error: null }));
console.log (view ({ status: "loading" , query: "нон" , data: [], error: null }));
console.log (view ({ status: "error" , query: "нон" , data: [], error: "Сервер не отвечает" }));
console.log (view ({ status: "success" , query: "борщ" , data: [], error: null }));
console.log (view ({ status: "success" , query: "н" , data: ["Курутоб" , "Нон" ], error: null }));
Натиҷаи интизоршаванда Введите запрос
Загрузка…
Ошибка: Сервер не отвечает
Ничего не найдено по запросу «борщ»
Блюда: Курутоб, Нон
Ишора 1 Занҷири if бо return-и барвақт (дарси 5): аввал idle, loading, error ва танҳо баъд санҷиши дарозии state.data.
Ишора 2 Номҳо бо вергул ва фосила: state.data.join(", "). Дархост дар нохунакҳои арча бо сатри қолабӣ гузошта мешавад: `... «${state.query}»`.
○ Ҳалшуда қайд кардан 💬 Дар бораи ин вазифа пурсидан
ВАЗИФАИ 1.3 Хаторо ёб: «Ничего не найдено» ба ҷои боркунӣ миёна
Корбарон шикоят мекунанд: ҳангоми боркунӣ онҳо «Ничего не найдено»-ро мебинанд ва ҳангоми хато — ҳам. Сабабро дар тартиби санҷишҳо ёбед ва функсияро чунон ислоҳ кунед, ки чоп бо интизоршаванда мувофиқат кунад.
Таҳрир кардан мумкин аст · Ctrl + Enter ↺ Аз нав ▶ Иҷро кардан
function view (state) {
if (state.data.length === 0 ) return "Ничего не найдено" ;
if (state.status === "loading" ) return "Загрузка…" ;
if (state.status === "error" ) return `Ошибка: ${state.error}` ;
return state.data.join (", " );
}
console.log (view ({ status: "loading" , data: [], error: null }));
console.log (view ({ status: "error" , data: [], error: "Нет сети" }));
console.log (view ({ status: "success" , data: [], error: null }));
console.log (view ({ status: "success" , data: ["Нон" ], error: null }));
Натиҷаи интизоршаванда Загрузка…
Ошибка: Нет сети
Ничего не найдено
Нон
Ишора 1 Барои loading кадом санҷиш кор мекунад, агар рӯйхат холӣ бошад? Худи аввалӣ — ва функсия мебарояд.
Ишора 2 Сатри data.length === 0-ро ба ҷои сеюм гузаронед: баъд аз loading ва error.
○ Ҳалшуда қайд кардан 💬 Дар бораи ин вазифа пурсидан
ВАЗИФАИ 1.4 Гузариш байни ҳолатҳо миёна
Функсияи next(state, event)-ро нависед: вай ҳолати ҷорӣ ва ҳодисаро мегирад ва ҳолати навро бармегардонад (объекти кӯҳнаро тағйир намедиҳем). Ҳодисаҳо: start → status: "loading", error: null, маълумоти пештара мемонад; done → status: "success", дар data — event.data, хато нест; fail → status: "error", дар error — event.error, маълумоти пештара мемонад.
Таҳрир кардан мумкин аст · Ctrl + Enter ↺ Аз нав ▶ Иҷро кардан
// function next(state, event) { ... }
let s = { status: "idle" , data: [], error: null };
const events = [
{ type: "start" },
{ type: "done" , data: ["Нон" , "Курутоб" ] },
{ type: "start" },
{ type: "fail" , error: "Нет сети" },
];
for (const e of events) {
s = next (s, e);
console.log (e.type, "→" , s.status, s.data.length, s.error);
}
Натиҷаи интизоршаванда start → loading 0 null
done → success 2 null
start → loading 2 null
fail → error 2 Нет сети
Ишора 1 Се if аз рӯи event.type, дар ҳар кадом — return-и объекти нав. Барои нигоҳ доштани маълумоти пештара ҳолати кӯҳнаро нусха кунед: { ...state, status: "loading" }.
Ишора 2 Барои done нусха кардани ҳолати кӯҳна лозим нест — ҳар се майдон аз нав муайян мешавад: { status: "success", data: event.data, error: null }.
○ Ҳалшуда қайд кардан 💬 Дар бораи ин вазифа пурсидан
02 Дархост, хатоҳо ва «сервери суст»
Акнун боркунии воқеиро пайваст мекунем. Аз дарсҳои гузашта схемаро медонед: функсияи async, await fetch(...), try / catch. Ба он ҳолатҳоро илова мекунем: пеш аз дархост loading-ро мемонем, баъд аз — success ё error, finally бошад дар ин ҷо қариб лозим нест: ҳар ҳолат ошкоро гузошта мешавад.
API-и воқеӣ: dummyjson.com (интернет лозим аст) · Ctrl + Enter ↺ Аз нав ▶ Иҷро кардан
async function searchProducts (query) {
console.log ("loading" );
try {
const res = await fetch (`https://dummyjson.com/products/search?q=${query}&limit=3` );
const data = await res.json ();
console.log (`success: ${data.products.length} из ${data.total}` );
} catch (err) {
console.log (`error: ${err.message}` );
}
}
searchProducts ("phone" );
Ду хатои гуногун — ва fetch онҳоро на ҳамон тавр фарқ мекунад, ки шумо гумон мекунед
Ин доме аст, ки қариб ҳар навомӯз ба он меафтад. Дархост ду навъи гуногуни ноком дорад:
Шабака нест Интернет гум шуд, сервер ёфт нашуд, дархост нарасид. Он гоҳ fetch промисро рад мекунад — идоракунӣ ба catch меравад.
Сервер гуфт «не» Дархост расид, аммо сервер рамзи 404 («ёфт нашуд») ё 500 («назди мо ҳама чиз вайрон шуд») баргардонд. Барои fetch ин муваффақият аст: ҷавоб гирифта шуд! Промис иҷро мешавад, ба catch намеафтем.
Барои ҳамин баъд аз fetch ҳамеша хусусияти res.ok-ро санҷед — вай барои рамзҳои 200–299 ба true баробар аст. Агар не — хаторо худатон бипартоед:
API-и воқеӣ: моли 999999 нест → рамзи 404 (интернет лозим аст) · Ctrl + Enter ↺ Аз нав ▶ Иҷро кардан
async function loadProduct (id) {
try {
const res = await fetch (`https://dummyjson.com/products/${id}` );
console.log ("status:" , res.status, "ok:" , res.ok);
if (!res.ok) throw new Error (`Сервер ответил кодом ${res.status}` );
const product = await res.json ();
console.log ("success:" , product.title);
} catch (err) {
console.log ("error:" , err.message);
}
}
loadProduct (999999 );
Кӯшиш кунед 999999-ро бо 1 иваз кунед: рамз 200 мешавад ва шумо номи молро мебинед.
Қиёс аз ҳаёт
Шумо курйерро ба мағоза дар Бозори Сабз фиристодед. Агар роҳро банданд ва курйер нарасид — ин «шабака нест» аст (catch). Агар курйер расид ва бо гуфтаи «ин мол дар фурӯш нест» баргашт — вай бомуваффақият баргашт, аммо натиҷа бад аст. Маҳз ҳамин тавр fetch фикр мекунад: курйер баргашт — пас муваффақият. Санҷидани он ки вай дақиқан чӣ овард, кори шумост.
«Сервери суст» дар коди мо
API-и воқеӣ гоҳ тез, гоҳ суст ҷавоб медиҳад, гоҳ умуман ҷавоб намедиҳад. Барои он ки дарсҳо ва вазифаҳо софдилона ва барои ҳама якхела бошанд , мо ба худамон сервери «қалбакӣ» месозем — функсияи fakeSearch. Вай хӯрокро дар массиви хурд меҷӯяд, баъд аз вақти додашуда ҷавоб медиҳад ва аз рӯи дархости «ошибка» вайрон шуда метавонад. Касбиён ҳам ҳамин тавр мекунанд: то даме ки сервери воқеӣ нест (ё ноустувор аст), интерфейсро бо ҷойгузин месанҷанд.
Ҷойгузинро таҳлил мекунем
Онро пурра хондан ҳатмӣ нест, аммо се ҷои муҳим ҳаст. setTimeout таъхири шабакаро тақлид мекунад (ms миллисония). Агар дархост бо номи "ошибка" фиристода шавад — промис рад мешавад, мисли ҳангоми қатъи шабака. Параметри signal барои бекор кардан лозим аст — дар бораи он дар қисми 5; ҳоло онро нодида гирифтан мумкин аст. Дар ҳамаи мисолҳои поён блоки «сервер» як хел аст — онро оддӣ пурра нусха кунед.
«Сервери» суст-и мо · Ctrl + Enter ↺ Аз нав ▶ Иҷро кардан
// ↓ «сервер»: ҷустуҷӯи суст дар меню — тағйир надиҳед
const DISHES = ["Оши палав" , "Самбуса с мясом" , "Самбуса с тыквой" , "Курутоб" , "Шакароб" , "Нон" ];
function fakeSearch (query, { signal, ms = 200 } = {}) {
return new Promise ((resolve, reject) => {
if (signal && signal.aborted) return reject (signal.reason);
const timer = setTimeout (() => {
if (query === "ошибка" ) return reject (new Error ("Нет соединения с сервером" ));
resolve (DISHES.filter ((d) => d.toLowerCase ().includes (query.toLowerCase ())));
}, ms);
if (signal) {
signal.addEventListener ("abort" , () => {
clearTimeout (timer);
reject (signal.reason);
});
}
});
}
fakeSearch ("самбуса" ).then ((dishes) => console.log ("Нашли:" , dishes.join (", " )));
fakeSearch ("борщ" ).then ((dishes) => console.log ("Нашли по «борщ»:" , dishes.length));
fakeSearch ("ошибка" ).catch ((err) => console.log ("Сбой:" , err.message));
Давраи пурра: loading → success / empty / error
Ҳамаро ба як функсия мечаспонем. Ҳар сатри ҳолат — як «тасвир»-и экран аст. Аз паси тартиб дар консол назар кунед:
Се натиҷаи як дархост · Ctrl + Enter ↺ Аз нав ▶ Иҷро кардан
// ↓ «сервер»: ҷустуҷӯи суст дар меню — тағйир надиҳед
const DISHES = ["Оши палав" , "Самбуса с мясом" , "Самбуса с тыквой" , "Курутоб" , "Шакароб" , "Нон" ];
function fakeSearch (query, { signal, ms = 200 } = {}) {
return new Promise ((resolve, reject) => {
if (signal && signal.aborted) return reject (signal.reason);
const timer = setTimeout (() => {
if (query === "ошибка" ) return reject (new Error ("Нет соединения с сервером" ));
resolve (DISHES.filter ((d) => d.toLowerCase ().includes (query.toLowerCase ())));
}, ms);
if (signal) {
signal.addEventListener ("abort" , () => {
clearTimeout (timer);
reject (signal.reason);
});
}
});
}
async function load (query) {
console.log ("loading…" );
try {
const items = await fakeSearch (query);
if (items.length === 0 ) {
console.log ("empty: ничего не найдено" );
} else {
console.log (`success: ${items.length} шт.` );
}
} catch (err) {
console.log (`error: ${err.message}` );
}
}
async function main () {
await load ("курутоб" );
await load ("борщ" );
await load ("ошибка" );
}
main ();
Дом
Ҳамаро ба як catch партофтан ва ба корбар err.message-ро чунон ки ҳаст навиштан — фикри бад. Матнҳои мисли Failed to fetch ё Unexpected token < in JSON метарсонанд ва чизеро шарҳ намедиҳанд. Ибораи инсониро нишон диҳед («Менюро бор кардан муяссар нашуд. Интернетро санҷед ва такрор кунед»), матни «техникӣ»-ро барои console.error гузоред — вай ба шумо лозим аст, на ба корбар.
Такрор ҳангоми нокомӣ
Азбаски хато кори зуд-зуд аст, ба корбар тугмаи «Такрор кардан» лозим аст. Дар код ин оддӣ як даъвати дигари ҳамон load аст. Гоҳо такрорро худкор мекунанд — масалан, то се кӯшиши пай дар пай. Мо инро дар вазифаи 2.4 машқ мекунем.
Муфассалтар хонед: learn.javascript.ru: Fetch · MDN: Response.ok
Амал: мустаҳкам мекунем
Дар ҳамаи вазифаҳои поён блоки «сервер» аллакай гузошта шудааст — онро тағйир надиҳед.
ВАЗИФАИ 2.1 Дархости аввалин ба ҷойгузин осон
Функсияи async-и main-ро анҷом диҳед: ҷавоби fakeSearch("самбуса")-ро интизор шавед ва номи хӯрокҳои ёфтшударо бо вергул ва фосила чоп кунед.
Таҳрир кардан мумкин аст · Ctrl + Enter ↺ Аз нав ▶ Иҷро кардан
// ↓ «сервер»: ҷустуҷӯи суст дар меню — тағйир надиҳед
const DISHES = ["Оши палав" , "Самбуса с мясом" , "Самбуса с тыквой" , "Курутоб" , "Шакароб" , "Нон" ];
function fakeSearch (query, { signal, ms = 200 } = {}) {
return new Promise ((resolve, reject) => {
if (signal && signal.aborted) return reject (signal.reason);
const timer = setTimeout (() => {
if (query === "ошибка" ) return reject (new Error ("Нет соединения с сервером" ));
resolve (DISHES.filter ((d) => d.toLowerCase ().includes (query.toLowerCase ())));
}, ms);
if (signal) {
signal.addEventListener ("abort" , () => {
clearTimeout (timer);
reject (signal.reason);
});
}
});
}
async function main () {
// await fakeSearch(...) ва console.log
}
main ();
Натиҷаи интизоршаванда Самбуса с мясом, Самбуса с тыквой
Ишора 1 const dishes = await fakeSearch("самбуса"); — await дар дохили функсияи async иҷозат дода шудааст.
Ишора 2 dishes — массиви сатрҳо. Ба як сатр бо вергул пайваст кардани он join(", ") кӯмак мекунад.
○ Ҳалшуда қайд кардан 💬 Дар бораи ин вазифа пурсидан
ВАЗИФАИ 2.2 Функсияи load бо се натиҷа миёна
async function load(query)-ро нависед. Вай аввал loading-ро чоп мекунад; баъд хӯрокҳоро дархост мекунад; агар ҳадди ақал яке ёфт шуд — success: N-ро чоп мекунад (N — чандто ёфт шуд); агар рӯйхат холӣ бошад — empty; агар дархост афтод — error: ва матни хато. Даъватҳо дар main аллакай омодаанд.
Таҳрир кардан мумкин аст · Ctrl + Enter ↺ Аз нав ▶ Иҷро кардан
// ↓ «сервер»: ҷустуҷӯи суст дар меню — тағйир надиҳед
const DISHES = ["Оши палав" , "Самбуса с мясом" , "Самбуса с тыквой" , "Курутоб" , "Шакароб" , "Нон" ];
function fakeSearch (query, { signal, ms = 200 } = {}) {
return new Promise ((resolve, reject) => {
if (signal && signal.aborted) return reject (signal.reason);
const timer = setTimeout (() => {
if (query === "ошибка" ) return reject (new Error ("Нет соединения с сервером" ));
resolve (DISHES.filter ((d) => d.toLowerCase ().includes (query.toLowerCase ())));
}, ms);
if (signal) {
signal.addEventListener ("abort" , () => {
clearTimeout (timer);
reject (signal.reason);
});
}
});
}
// async function load(query) { ... }
async function main () {
await load ("курутоб" );
await load ("ошибка" );
await load ("борщ" );
}
main ();
Натиҷаи интизоршаванда loading
success: 1
loading
error: Нет соединения с сервером
loading
empty
Ишора 1 Скелет: console.log("loading"); try { const items = await fakeSearch(query); ... } catch (err) { ... }.
Ишора 2 Дар дохили try items.length === 0-ро санҷед — ин empty аст, вагарна success. Матни хато дар err.message аст.
○ Ҳалшуда қайд кардан 💬 Дар бораи ин вазифа пурсидан
ВАЗИФАИ 2.3 Сервер бо хато ҷавоб дод: ok-ро месанҷем миёна
Функсияи fakeHttp(status) мисли fetch-и воқеӣ рафтор мекунад: ҳамеша ҷавобро бо майдонҳои ok ва status бармегардонад, ҳатто агар статус 404 ё 500 бошад. async function getJson(status)-ро нависед: ҷавобро гиред; агар res.ok дурӯғ бошад — Error-ро бо матни HTTP 404 (бо рамзи воқеӣ) бипартоед; вагарна натиҷаи res.json()-ро баргардонед. Дар main натиҷаи ҳар даъват дар консол тарҳрезӣ мешавад.
Таҳрир кардан мумкин аст · Ctrl + Enter ↺ Аз нав ▶ Иҷро кардан
const fakeHttp = (status) =>
Promise.resolve ({
ok: status >= 200 && status < 300 ,
status,
json: () => Promise.resolve ({ dishes: 3 }),
});
// async function getJson(status) { ... }
async function main () {
for (const status of [200 , 404 , 500 ]) {
try {
const data = await getJson (status);
console.log (`${status} → ${data.dishes} блюда` );
} catch (err) {
console.log (`${status} → ошибка: ${err.message}` );
}
}
}
main ();
Натиҷаи интизоршаванда 200 → 3 блюда
404 → ошибка: HTTP 404
500 → ошибка: HTTP 500
Ишора 1 Бе санҷиши ok сатрҳо бо 404 ва 500 «3 хӯрок»-ро нишон медиҳанд: барои «fetch» ин муваффақият аст.
Ишора 2 if (!res.ok) throw new Error(`HTTP ${res.status}`); — ва баъд аз ин санҷиш res.json()-ро бо оромӣ баргардонидан мумкин аст.
○ Ҳалшуда қайд кардан 💬 Дар бораи ин вазифа пурсидан
ВАЗИФАИ 2.4 Се кӯшиш душвор
Шабака дар маршрутка ноустувор аст. flakySearch ду бори аввал бо хатои Нет сети меафтад, бори сеюм кор мекунад. async function searchWithRetry(maxAttempts)-ро нависед: вай дархостро дар давра мекӯшад; ҳангоми хато попытка N: ошибка (текст)-ро чоп мекунад ва ба давраи навбатӣ мегузарад; ҳангоми муваффақият попытка N: успех, найдено K-ро чоп мекунад ва натиҷаро бармегардонад. Агар ҳамаи кӯшишҳо тамом шаванд — сдаёмся-ро чоп мекунад ва null бармегардонад.
Таҳрир кардан мумкин аст · Ctrl + Enter ↺ Аз нав ▶ Иҷро кардан
let calls = 0 ;
function flakySearch () {
calls++;
return new Promise ((resolve, reject) =>
setTimeout (() => (calls < 3 ? reject (new Error ("Нет сети" )) : resolve (["Курутоб" ])), 100 )
);
}
// async function searchWithRetry(maxAttempts) { ... }
searchWithRetry (4 );
Натиҷаи интизоршаванда попытка 1: ошибка (Нет сети)
попытка 2: ошибка (Нет сети)
попытка 3: успех, найдено 1
Ишора 1 Давраи for (let attempt = 1; attempt <= maxAttempts; attempt++), дар дохили он — try / catch. Муваффақият — return рост аз try, вай фавран функсия ва давраро тамом мекунад.
Ишора 2 Дар catch танҳо сатрро чоп мекунем ва чизеро барнамегардонем — давра худаш ба кӯшиши навбатӣ мегузарад. Сатри «сдаёмся» ва return null баъд аз давра меистанд: ба ин ҷо танҳо дар сурате мерасем, ки ягон кӯшиш муваффақ нашуд.
○ Ҳалшуда қайд кардан 💬 Дар бораи ин вазифа пурсидан
03 Skeleton: ҷойгузинҳои хокистарӣ ба ҷои холигӣ
Оддитарин ҳолати боркунӣ — навиштаи «Загрузка…» ё чархак. Аммо вай камбудӣ дорад: саҳифа «ҷаст мезанад». Аввал дар экран навиштаи хурд, баъд ногаҳон рӯйхати дароз пайдо мешавад ва тамоми мазмун ба поён мекӯчад. Роҳи ҳал — skeleton («скелет»): дар ҷои кортҳои оянда росткунҷаҳои хокистарии ҳамон андоза нишон медиҳем. Корбар пешакӣ мебинад, ки саҳифа чӣ гуна хоҳад буд , вақте маълумот меояд — ҷойгузинҳо оддӣ бо кортҳои воқеӣ дар ҳамон ҷойҳо иваз мешаванд.
Қиёс аз ҳаёт
Шумо ба тӯй дар чойхона омадед, аммо дастурхон ҳанӯз чида нашудааст. Аммо ғилофи миз аллакай густурда шудааст ва болои он табақҳои холӣ — ба шумораи меҳмонон — гузошта шудаанд. Шумо фавран мебинед, ки дар куҷо менишинед ва чанд ҷой ҳаст. Баъд пешхизматон оддӣ хӯрокро ба табақҳои тайёр мегузоранд — миз аз нав чида намешавад. Skeleton — ҳамон табақҳои холӣ аст.
Skeleton бо CSS-и пок
JavaScript дар ин ҷо танҳо барои як чиз лозим аст: шумораи лозимаи блокҳоро ба саҳифа гузоштан. Тамоми «намуд»-ро CSS месозад. Сирри милликунӣ — градиент , ки бо ёрии аниматсия аз чап ба рост «меҳазад»:
CSS барои skeleton
.skeleton {
/* замини хокистарии равшан бо рахи равшантар дар мобайн */
background : linear-gradient(90deg , #e6e9ef 25 %, #f4f6fa 37 %, #e6e9ef 63 %);
background-size : 400 % 100 %; /* градиент 4 маротиба аз блок васеътар */
animation : shimmer 1.2s ease infinite;
border-radius : 8px ;
color : transparent; /* барои ҳолате, ки дар дохил матн пайдо шавад */
}
@keyframes shimmer {
0 % { background-position : 100 % 0 ; }
100 % { background-position : 0 0 ; }
}
/* ба корбароне, ки аниматсия ба онҳо нохуш аст, эҳтиром мегузорем */
@media (prefers-reduced-motion: reduce) {
.skeleton { animation : none; }
}
Градиент аз блок васеътар. background-size: 400% заминро чор маротиба аз худи блок васеътар мекунад — танҳо порчаи он намоён аст.
Аниматсия заминро ҳаракат медиҳад. @keyframes shimmer background-position-ро тағйир медиҳад — «рахи равшан» оҳиста аз болои блок мегузарад.
Андозаҳоро корт муайян мекунад. Ҷойгузин бояд ҳамон баландӣ ва бари корти воқеӣ дошта бошад — вагарна саҳифа ба ҳар ҳол «ҷаст мезанад».
Дар бораи дастрасӣ фаромӯш накунед. Ба контейнер aria-busy="true"-ро то боркунӣ мемонем ва "false" — вақте тамом шуд: хонандаҳои экран мефаҳманд, ки мазмун навсозӣ мешавад.
Ба мисоли тайёр нигаред. «Иҷро кардан»-ро пахш кунед — аввал кортҳои хокистарии милликунандаро мебинед ва баъд аз 0,8 сония — хӯрокҳои воқеӣ.
Skeleton → кортҳо · Ctrl + Enter ↺ Аз нав ▶ Иҷро кардан
index.html style.css script.js
< div id ="list" class ="list" aria-busy ="true" > </ div >
.list { display : grid; gap : 10px ; }
.card { height : 54px ; padding : 14px 16px ; border : 2px solid #ccd3df ; border-radius : 12px ; background : #fff ; font-weight : 700 ; box-sizing : border-box; }
.skeleton {
border-color : transparent;
color : transparent;
background : linear-gradient(90deg , #e6e9ef 25 %, #f4f6fa 37 %, #e6e9ef 63 %);
background-size : 400 % 100 %;
animation : shimmer 1.2s ease infinite;
}
@keyframes shimmer { 0 % { background-position : 100 % 0 ; } 100 % { background-position : 0 0 ; } }
@media (prefers-reduced-motion: reduce) { .skeleton { animation : none; } }
const list = document.querySelector ("#list" );
// 1. боркунӣ: се ҷойгузин
for (let i = 0 ; i < 3 ; i++) {
const stub = document.createElement ("div" );
stub.className = "card skeleton" ;
list.append (stub);
}
// 2. баъд аз 0,8 сония маълумот «меояд»
setTimeout (() => {
const cards = ["Курутоб" , "Самбуса" , "Шакароб" ].map ((name) => {
const card = document.createElement ("div" );
card.className = "card" ;
card.textContent = name;
return card;
});
list.replaceChildren (... cards);
list.setAttribute ("aria-busy" , "false" );
}, 800 );
Пешнамоиш Пӯшонидан
Барои дидани натиҷа «Иҷро кардан»-ро пахш кунед.
Кай skeleton, кай чархак
Skeleton хуб аст, вақте медонед, ки натиҷа чӣ гуна менамояд: рӯйхати кортҳо, ҷадвал, профил. Чархак (спиннер) ё «Загрузка…»-и кӯтоҳ — вақте натиҷа номуайян ё хурд аст (масалан, тугмаи «Захира кардан»). Бо skeleton кор карда, ҳамон қадар ҷойгузин нишон диҳед, ки одатан кортҳо дар экран ҷо мешаванд, — зиёд не.
Skeleton — ягона ҳолати «зебо» нест
Ҳолатҳои дигарро низ бояд оро дод, на ин ки матни «луч» гузошт:
Холӣ (empty): ибораи кӯтоҳ ва ишора, ки чӣ кор кардан лозим: «Ничего не найдено. Номи дигарро кӯшиш кунед».
Хато (error): сабаби фаҳмо ва тугмаи «Такрор кардан» . Ранги сурх — ҳатмӣ нест, асосӣ — равшан бошад, ки минбаъд чӣ кор кардан мумкин аст.
Оғоз (idle): даъват ба амал: «Номи хӯрокро ворид кунед».
Хатои маъмул
Нест кардани ҷойгузинҳоро фаромӯш кардан. Агар ҳангоми ҷавоби сервер кортҳоро тавассути append илова кунед, на ин ки мазмунро тавассути replaceChildren иваз кунед, блокҳои хокистарӣ дар саҳифа мемонанд, кортҳои воқеӣ бошанд дар зери онҳо пайдо мешаванд. Ҳамеша мазмунро пурра иваз кунед.
Муфассалтар хонед: MDN: истифодаи аниматсияҳои CSS · MDN: prefers-reduced-motion
Амал: мустаҳкам мекунем
Дар ҳар вазифа саҳифаи хурд кор мекунад: код дар тарафи чап (ҷадвалҳои index.html, style.css, script.js), натиҷа дар тарафи рост, сатрҳои санҷишӣ — дар консол. CSS барои skeleton аллакай тайёр аст.
ВАЗИФАИ 3.1 Се ҷойгузин осон
Функсияи showSkeleton(count)-ро анҷом диҳед: вай мазмуни #list-ро бо count блоки <div class="card skeleton"></div> иваз мекунад ва ба #list атрибути aria-busy="true"-ро мемонад. Сатрҳои санҷишии поён шумораи блокҳо, шумораи ҷойгузинҳо ва қимати атрибутро чоп мекунанд.
showSkeleton · Ctrl + Enter ↺ Аз нав ▶ Иҷро кардан
index.html style.css script.js
< div id ="list" class ="list" > </ div >
.list { display : grid; gap : 10px ; }
.card { height : 54px ; border : 2px solid #ccd3df ; border-radius : 12px ; background : #fff ; box-sizing : border-box; padding : 14px 16px ; font-weight : 700 ; }
.skeleton {
border-color : transparent;
background : linear-gradient(90deg , #e6e9ef 25 %, #f4f6fa 37 %, #e6e9ef 63 %);
background-size : 400 % 100 %;
animation : shimmer 1.2s ease infinite;
}
@keyframes shimmer { 0 % { background-position : 100 % 0 ; } 100 % { background-position : 0 0 ; } }
const list = document.querySelector ("#list" );
function showSkeleton (count) {
// list-ро тоза кунед, count блоки div.card.skeleton илова кунед
// ва aria-busy="true"-ро гузоред
}
showSkeleton (3 );
// --- санҷиш (нест накунед) ---
console.log (list.children.length);
console.log (list.querySelectorAll (".skeleton" ).length);
console.log (list.getAttribute ("aria-busy" ));
Пешнамоиш Пӯшонидан
Барои дидани натиҷа «Иҷро кардан»-ро пахш кунед.
Натиҷаи интизоршаванда 3
3
true
Ишора 1 Давраи оддии for барои count қадам: дар ҳар қадам — document.createElement("div"), баъд className = "card skeleton" ва list.append(...).
Ишора 2 Барои аввал нест кардани кӯҳна list.replaceChildren()-ро даъват кунед. Атрибут чунин гузошта мешавад: list.setAttribute("aria-busy", "true").
○ Ҳалшуда қайд кардан 💬 Дар бораи ин вазифа пурсидан
ВАЗИФАИ 3.2 Ҷойгузинҳо бо кортҳо иваз мешаванд миёна
showCards(items)-ро анҷом диҳед: вай мазмуни #list-ро пурра бо кортҳои div.card (барои ҳар хӯрок як-як, ном — дар textContent) иваз мекунад ва aria-busy="false"-ро мемонад. Баъд аз даъвати fakeSearch шумо бояд бубинед, ки ҷойгузин намонд.
Skeleton → cards · Ctrl + Enter ↺ Аз нав ▶ Иҷро кардан
index.html style.css script.js
< div id ="list" class ="list" > </ div >
.list { display : grid; gap : 10px ; }
.card { height : 54px ; border : 2px solid #ccd3df ; border-radius : 12px ; background : #fff ; box-sizing : border-box; padding : 14px 16px ; font-weight : 700 ; }
.skeleton {
border-color : transparent;
background : linear-gradient(90deg , #e6e9ef 25 %, #f4f6fa 37 %, #e6e9ef 63 %);
background-size : 400 % 100 %;
animation : shimmer 1.2s ease infinite;
}
@keyframes shimmer { 0 % { background-position : 100 % 0 ; } 100 % { background-position : 0 0 ; } }
// ↓ «сервер»: ҷустуҷӯи суст дар меню — тағйир надиҳед
const DISHES = ["Оши палав" , "Самбуса с мясом" , "Самбуса с тыквой" , "Курутоб" , "Шакароб" , "Нон" ];
function fakeSearch (query, { signal, ms = 200 } = {}) {
return new Promise ((resolve, reject) => {
if (signal && signal.aborted) return reject (signal.reason);
const timer = setTimeout (() => {
if (query === "ошибка" ) return reject (new Error ("Нет соединения с сервером" ));
resolve (DISHES.filter ((d) => d.toLowerCase ().includes (query.toLowerCase ())));
}, ms);
if (signal) {
signal.addEventListener ("abort" , () => {
clearTimeout (timer);
reject (signal.reason);
});
}
});
}
const list = document.querySelector ("#list" );
const count = (selector) => list.querySelectorAll (selector).length;
function showSkeleton (n) {
const stubs = Array.from ({ length: n }, () => {
const stub = document.createElement ("div" );
stub.className = "card skeleton" ;
return stub;
});
list.replaceChildren (... stubs);
list.setAttribute ("aria-busy" , "true" );
}
function showCards (items) {
// мазмуни list-ро бо кортҳои div.card иваз кунед ва aria-busy="false"-ро гузоред
}
showSkeleton (3 );
console.log (`скелетонов: ${count(".skeleton")}` );
fakeSearch ("самбуса" ).then ((items) => {
showCards (items);
console.log (`скелетонов: ${count(".skeleton")}, карточек: ${count(".card")}` );
console.log (list.getAttribute ("aria-busy" ));
});
Пешнамоиш Пӯшонидан
Барои дидани натиҷа «Иҷро кардан»-ро пахш кунед.
Натиҷаи интизоршаванда скелетонов: 3
скелетонов: 0, карточек: 2
false
Ишора 1 Дар дохили showCards массиви номҳоро тавассути items.map(...) ба массиви кортҳо табдил диҳед — ҳамон тавр, ки showSkeleton бо блокҳои худ мекунад.
Ишора 2 Пурра иваз кардани мазмун — list.replaceChildren(...cards). Он гоҳ ҷойгузинҳо худашон нопадид мешаванд ва дастӣ нест кардани онҳо лозим нест.
○ Ҳалшуда қайд кардан 💬 Дар бораи ин вазифа пурсидан
ВАЗИФАИ 3.3 Чор ҳолат дар тамға душвор
Функсияи render(state)-ро анҷом диҳед, ки мазмуни #box-ро барои ҳар ҳолат мекашад. Аввал вай #box-ро тоза мекунад ва box.dataset.state-ро мемонад: ин "loading", "error" ё "success" аст — аммо агар статус success бошад ва маълумот набошад, пас "empty". Баъд мекашад: барои loading — се блоки div.skeleton; барои error — <p class="error">Не удалось загрузить</p> ва тугмаи <button id="retry">Повторить</button>; барои empty — <p class="empty">Ничего не найдено</p>; барои success — барои ҳар унсури data як div.dish.
render(state) · Ctrl + Enter ↺ Аз нав ▶ Иҷро кардан
index.html style.css script.js
< div id ="box" class ="box" > </ div >
.box { display : grid; gap : 8px ; }
.skeleton { height : 40px ; border-radius : 10px ; background : linear-gradient(90deg , #e6e9ef 25 %, #f4f6fa 37 %, #e6e9ef 63 %); background-size : 400 % 100 %; animation : shimmer 1.2s ease infinite; }
@keyframes shimmer { 0 % { background-position : 100 % 0 ; } 100 % { background-position : 0 0 ; } }
.dish { padding : 10px 14px ; border : 2px solid #ccd3df ; border-radius : 10px ; background : #fff ; font-weight : 700 ; }
.error { color : #c0392b ; font-weight : 700 ; margin : 0 ; }
.empty { color : #6b7488 ; margin : 0 ; }
#retry { justify-self : start; border : 2px solid #1c2230 ; background : #fff ; border-radius : 8px ; padding : 6px 12px ; cursor : pointer; font-weight : 700 ; }
const box = document.querySelector ("#box" );
function render (state) {
// 1. box-ро тоза кунед ва box.dataset.state-ро гузоред
// 2. вобаста ба ҳолат чизи лозимаро кашед
}
// --- санҷиш (нест накунед) ---
const states = [
{ status: "loading" , data: [] },
{ status: "error" , data: [], error: "Нет сети" },
{ status: "success" , data: [] },
{ status: "success" , data: ["Курутоб" , "Нон" , "Шакароб" ] },
];
for (const s of states) {
render (s);
console.log (box.dataset.state, box.children.length, !!box.querySelector ("#retry" ));
}
Пешнамоиш Пӯшонидан
Барои дидани натиҷа «Иҷро кардан»-ро пахш кунед.
Натиҷаи интизоршаванда loading 3 false
error 2 true
empty 1 false
success 3 false
Ишора 1 Аввал «намуд»-и ҳолатро ҳисоб кунед: const kind = state.status === "success" && state.data.length === 0 ? "empty" : state.status; — ва онро ба box.dataset.state гузоред.
Ишора 2 Минбаъд — занҷири if / else if аз рӯи kind. Дар ҳар шоха унсурҳоро тавассути createElement созед, className ва textContent-ро муқаррар кунед, дар охир — box.append(...). Ба тугма id = "retry" лозим аст.
○ Ҳалшуда қайд кардан 💬 Дар бораи ин вазифа пурсидан
04 Debounce: функсияро ба ҳар атса насозед
Сатри ҷустуҷӯи «дар парвоз»: корбар «плов»-ро менависад ва натиҷаҳо аллакай дар ҷараён тағйир меёбанд. Оддитарин роҳи ҳал — ба ҳодисаи input гӯш додан ва барои ҳар ҳарф дархост фиристодан. Аммо одам зуд менависад: «п», «пл», «пло», «плов» — ин чор дархост дар як сония аст, ки аз онҳо танҳо охирин лозим аст. Сервер сарбор мешавад, трафик сарф мешавад, ҷавобҳо бошанд омехта меоянд (дар бораи ин — дар қисми 5).
Роҳи ҳал debounce — «пахшкунии ларзиш» ном дорад: функсияро танҳо вақте иҷро мекунем, ки корбар барои вақти додашуда онро кашидан гузошт . Ҳар даъвати нав интизориро аз нав оғоз мекунад. То даме ки одам менависад — ҳеҷ чиз намешавад; 300 мс истод — дархост меравад.
Қиёс аз ҳаёт
Маршрутка дар истгоҳ. Ронанда ҳамин ки мусофири аввал даромад, намеравад. Вай интизор мешавад: дуюм даромад — боз интизор, сеюм даромад — боз интизор. Ва танҳо вақте чанд сония ҳеҷ кас намедарояд, ҳаракат мекунад. Ҳар мусофири нав таймери интизориро аз нав оғоз мекунад. Debounce — ин ронандаест, ки танҳо баъд аз танаффус ҳаракат мекунад.
Нишонаҳои хокистарӣ — пахши тугмаҳо (п, пл, пло, плов). Рахи зард — интизории танаффус. Нишонаҳои сабз — дархостҳо: танҳо баъд аз хомӯшӣ ва танҳо бо қимати охирин
Debounce-ро аз сифр менависем
Дар дохил ҳамагӣ се ғояи аллакай маълум ҳаст: функсия функсия бармегардонад , closure таймерро байни даъватҳо нигоҳ медорад, clearTimeout бошад интизории қаблиро бекор мекунад.
debounce пурра
function debounce (fn, wait) {
let timer; // як таймер барои ҳамаи даъватҳо — дар closure зиндагӣ мекунад
return function (... args) {
clearTimeout (timer); // даъвати нав: интизории гузаштаро бекор мекунем
timer = setTimeout (() => {
fn (... args); // танаффус дида шуд — функсияи воқеиро иҷро мекунем
}, wait);
};
}
Мо функсияи fn ва танаффуси wait-ро мегирем ва функсияи нав бармегардонем — онро ба ҳодиса мепайвандем.
Тағйирёбандаи timer дар берун аз функсияи баргардонидашаванда эълон шудааст. Барои ҳамин ҳамаи даъватҳо як тағйирёбандаи ягонаро мебинанд (closure, дарси 5).
Ҳангоми ҳар даъват аввал clearTimeout(timer) интизории даъвати гузаштаро нест мекунад, баъд setTimeout интизории нав месозад.
Танҳо интизории охирин кор мекунад — он, ки дар давоми wait миллисония касе барқарор накард.
...args ҳамаи аргументҳоро ба массив ҷамъ мекунад, fn(...args) бошад баргашта паҳн мекунад — ба ин тартиб debounce ба функсия бо ҳар шумораи параметрҳо мувофиқ аст.
Дар «менависандаи» одам месанҷем
Барои он ки натиҷа аз суръати навиштани шумо вобаста набошад, мо пахши тугмаҳоро аз рӯи ҷадвал ба нақша мегирем тавассути setTimeout. Ҷадвал: «п» дар 0 мс, «пл» дар 40 мс, «пло» дар 80 мс (зуд менависад), баъд танаффус ва «плов» дар 300 мс. Танаффуси debounce — 100 мс. Дар консол дида мешавад, ки воқеан чӣ кор кард.
Debounce дар амал · Ctrl + Enter ↺ Аз нав ▶ Иҷро кардан
function debounce (fn, wait) {
let timer;
return function (... args) {
clearTimeout (timer);
timer = setTimeout (() => fn (... args), wait);
};
}
const search = debounce ((q) => console.log (`поиск: ${q}` ), 100 );
[[0 , "п" ], [40 , "пл" ], [80 , "пло" ], [300 , "плов" ]].forEach (([at, q]) => {
setTimeout (() => {
console.log (`ввод: ${q}` );
search (q);
}, at);
});
Чопро таҳлил кунед. Чор «пахш» — аммо ҳамагӣ ду дархост . Се пахши аввал бо фосилаҳои 40 мс (камтар аз 100) мегузаштанд, барои ҳамин ҳар кадом таймерро аз нав оғоз мекард; баъд аз сеюм 100 мс хомӯшӣ гузашт — ва ҷустуҷӯи «пло» кор кард. Баъд танаффус то 300 мс — ва «плов» бо дархости алоҳида рафт.
Хатои маъмул
let timer-ро дар дохили функсияи баргардонидашаванда эълон кардан. Он гоҳ ҳар даъват timer-и худии ҷудогона мегирад, clearTimeout бошад холигиро бекор мекунад — ҳеҷ чиз барқарор намешавад ва ҳамаи даъватҳо кор мекунанд, фақат бо таъхир. Тағйирёбандаи таймер бояд дар берун, дар closure зиндагӣ кунад.
Кадом танаффусро интихоб кардан
Танаффус Чӣ тавр эҳсос мешавад 100–200 мс Қариб фавран, аммо ҳангоми навиштани зуд дархостҳо ба ҳар ҳол зуд-зуд мераванд 300–500 мс Миёнаи тиллоӣ барои ҷустуҷӯ: одатан ҳамин қадарро мегиранд 1000 мс ва бештар Интерфейс «овезон» менамояд — корбар намефаҳмад, ки ҷустуҷӯ кор мекунад ё не
Debounce ва throttle — як чиз нестанд
Debounce танаффусро интизор мешавад ва як маротиба баъд аз силсилаи даъватҳо кор мекунад (ҷустуҷӯ, худкор захира кардан). Throttle («дроссел») на зиёдтар аз як маротиба дар N мс ҳангоми силсила кор мекунад (варақ задани саҳифа, тағйири андозаи тиреза). Дар ҷустуҷӯ ба мо маҳз debounce лозим аст.
Кай debounce лозим нест
Барои амалҳо бо тугма («Ёфтан», «Фиристодан») танаффус лозим нест: одам бошуурона ва як маротиба пахш мекунад. Debounce — дору аз ҷараёни ҳодисаҳост: input, scroll, resize, mousemove.
Даъвати ба таъхир гузоштаро бекор кардан: cancel
Мешавад, ки на танҳо ба таъхир гузоштан, балки интизорро бекор кардан лозим мешавад: корбар майдонро тоза кард ё аз саҳифа баромад. Функсияҳо дар JavaScript низ объект мебошанд, ба онҳо хусусиятро «часпондан» мумкин аст. Усули cancel-ро илова мекунем:
JavaScript
function debounce (fn, wait) {
let timer;
function debounced (... args) {
clearTimeout (timer);
timer = setTimeout (() => fn (... args), wait);
}
debounced.cancel = () => clearTimeout (timer); // усул дар функсия
return debounced;
}
Муфассалтар хонед: learn.javascript.ru: декораторҳо ва равона кардани даъват · MDN: clearTimeout
Амал: мустаҳкам мекунем
Ҷадвали «пахшҳо» дар ҳар вазифа аллакай навишта шудааст — ба шумо танҳо debounce лозим аст. Ҳар иҷро аз як сония камтар мегирад.
ВАЗИФАИ 4.1 Ҷустуҷӯ ба ҳар ҳарф кор мекунад осон
Барномасоз debounce навишт, аммо ҷустуҷӯ ба ҳар пахш кор мекунад — фақат бо таъхир. Ёбед, ки ин версия аз дурусташ чӣ фарқ дорад ва чунон ислоҳ кунед, ки ҷустуҷӯ як маротиба — аз рӯи қимати охирин иҷро шавад.
Таҳрир кардан мумкин аст · Ctrl + Enter ↺ Аз нав ▶ Иҷро кардан
function debounce (fn, wait) {
return function (arg) {
let timer;
clearTimeout (timer);
timer = setTimeout (() => fn (arg), wait);
};
}
const search = debounce ((q) => console.log (`поиск: ${q}` ), 100 );
[[0 , "п" ], [40 , "пл" ], [80 , "пло" ]].forEach (([at, q]) => {
setTimeout (() => {
console.log (`ввод: ${q}` );
search (q);
}, at);
});
Натиҷаи интизоршаванда ввод: п
ввод: пл
ввод: пло
поиск: пло
Ишора 1 Чунон ки ҳаст иҷро кунед: ҷустуҷӯ се бор кор мекунад. Пас clearTimeout чизеро бекор намекунад. Барои чӣ? Бинед, тағйирёбандаи timer дар куҷо эълон шудааст.
Ишора 2 Ҳар даъват timer-и нави худашро месозад. Сатри let timer;-ро ба боло — ба функсияи debounce гузаронед, аммо пеш аз return function.
○ Ҳалшуда қайд кардан 💬 Дар бораи ин вазифа пурсидан
ВАЗИФАИ 4.2 Debounce-ро худамон менависем миёна
Функсияи debounce(fn, wait)-ро аз сифр нависед — бе нигоҳ кардан ба матни дарс, агар муяссар шавад. Функсияи баргардонидашаванда як аргументи arg-ро мегирад ва баъд аз танаффуси wait онро ба fn медиҳад. Ҷадвали поён: силсилаи зуди пахш, танаффус, баъд боз як пахш.
Таҳрир кардан мумкин аст · Ctrl + Enter ↺ Аз нав ▶ Иҷро кардан
// function debounce(fn, wait) { ... }
const search = debounce ((q) => console.log (`поиск: ${q}` ), 100 );
[[0 , "п" ], [40 , "пл" ], [80 , "пло" ], [300 , "плов" ]].forEach (([at, q]) => {
setTimeout (() => {
console.log (`ввод: ${q}` );
search (q);
}, at);
});
Натиҷаи интизоршаванда ввод: п
ввод: пл
ввод: пло
поиск: пло
ввод: плов
поиск: плов
Ишора 1 Се қадам: тағйирёбандаи timer-ро дар берун эълон кардан, функсия баргардонидан ва дар дохили он аввал таймери гузаштаро бекор кардан, баъд нав сохтан.
Ишора 2 return function (arg) { clearTimeout(timer); timer = setTimeout(() => fn(arg), wait); }; — тағйирёбандаи timer бо сатри боло, берун аз return эълон шудааст.
○ Ҳалшуда қайд кардан 💬 Дар бораи ин вазифа пурсидан
ВАЗИФАИ 4.3 Танаффуси хеле кӯтоҳ осон
Одам оҳиста менависад: як аломат ҳар 150 мс. Аввал пешгӯӣ кунед , ки ҳангоми танаффуси 200 мс чанд ҷустуҷӯ кор мекунад. Иҷро кунед ва санҷед. Баъд танаффусро чунон иваз кунед, ки ҷустуҷӯ баъд аз ҳар ҳарф кор кунад — яъне debounce амалан «кор накунад».
Таҳрир кардан мумкин аст · Ctrl + Enter ↺ Аз нав ▶ Иҷро кардан
function debounce (fn, wait) {
let timer;
return function (arg) {
clearTimeout (timer);
timer = setTimeout (() => fn (arg), wait);
};
}
const search = debounce ((q) => console.log (`поиск: ${q}` ), 200 );
[[0 , "п" ], [150 , "пл" ], [300 , "пло" ]].forEach (([at, q]) => {
setTimeout (() => {
console.log (`ввод: ${q}` );
search (q);
}, at);
});
Натиҷаи интизоршаванда пас аз ислоҳ ввод: п
поиск: п
ввод: пл
поиск: пл
ввод: пло
поиск: пло
Ишора 1 Пахши нав ҳар 150 мс меояд. Барои он ки ҷустуҷӯ пештар аз пахши оянда кор карда тавонад, танаффус бояд аз ин фосила кӯтоҳтар бошад.
Ишора 2 Танаффусро ба таври назаррас камтар аз 150 гузоред — масалан, 60. Он гоҳ таймер баъд аз 60 мс кор мекунад, пеш аз он ки ҳарфи оянда омада, онро барқарор кунад.
○ Ҳалшуда қайд кардан 💬 Дар бораи ин вазифа пурсидан
ВАЗИФАИ 4.4 Debounce бо якчанд аргумент миёна
Ҷустуҷӯ акнун дархост ва рақами саҳифаро мегирад: find(query, page). Debounce-и ҷорӣ танҳо аргументи аввалро минбаъд мегузаронад. Онро бо ...args чунон бознависед, ки ҳамаи аргументҳои даъвати охирин бирасанд.
Таҳрир кардан мумкин аст · Ctrl + Enter ↺ Аз нав ▶ Иҷро кардан
function debounce (fn, wait) {
let timer;
return function (arg) {
clearTimeout (timer);
timer = setTimeout (() => fn (arg), wait);
};
}
const find = debounce ((query, page) => console.log (`поиск: ${query}, страница ${page}` ), 100 );
find ("п" , 1 );
setTimeout (() => find ("пл" , 1 ), 40 );
setTimeout (() => find ("плов" , 2 ), 80 );
Натиҷаи интизоршаванда пас аз ислоҳ поиск: плов, страница 2
Ишора 1 Ҳоло функсия ҳамчун function (arg) эълон шудааст — вай як параметр мегирад, барои ҳамин page гум мешавад (undefined мешавад).
Ишора 2 Параметрро бо ...args иваз кунед, дар даъвати fn(arg) бошад fn(...args) нависед.
○ Ҳалшуда қайд кардан 💬 Дар бораи ин вазифа пурсидан
ВАЗИФАИ 4.5 Тугмаи «бекор кардан» дар debounce миёна
Ба debounce усули cancel-ро илова кунед, ки даъвати интизорро бекор мекунад. Ҷадвал: ду даъвати зуд, баъд бекор кардан, баъд — пас аз муддате — боз як даъват. Танҳо охирин бояд кор кунад.
Таҳрир кардан мумкин аст · Ctrl + Enter ↺ Аз нав ▶ Иҷро кардан
function debounce (fn, wait) {
let timer;
function debounced (... args) {
clearTimeout (timer);
timer = setTimeout (() => fn (... args), wait);
}
// усули debounced.cancel-ро илова кунед
return debounced;
}
const find = debounce ((q) => console.log (`поиск: ${q}` ), 100 );
setTimeout (() => { console.log ("вызов: п" ); find ("п" ); }, 0 );
setTimeout (() => { console.log ("вызов: пл" ); find ("пл" ); }, 40 );
setTimeout (() => { console.log ("отмена" ); find.cancel (); }, 80 );
setTimeout (() => { console.log ("вызов: плов" ); find ("плов" ); }, 300 );
Натиҷаи интизоршаванда вызов: п
вызов: пл
отмена
вызов: плов
поиск: плов
Ишора 1 Функсия дар JavaScript объект аст, барои ҳамин ба вай хусусият таъин кардан мумкин аст: debounced.cancel = .... Қимати таъиншаванда — функсияи тирдор.
Ишора 2 debounced.cancel = () => clearTimeout(timer); — вай timer-ро тавассути closure мебинад, мисли худи debounced.
○ Ҳалшуда қайд кардан 💬 Дар бораи ин вазифа пурсидан
05 AbortController: бекор кардани дархост
Debounce шумораи дархостҳоро кам мекунад, аммо мушкили асосиро бартараф намекунад: дархосте, ки аллакай рафтааст, «фаромӯш» кардан мумкин нест . Вай ба сервер мерасад ва бо ҷавоб бармегардад, ҳатто агар ин ҷавоб ба корбар аллакай лозим набошад. Ва ин ба ду нохушӣ меорад.
Мушкили 1. Кори зиёдатӣ
Корбар саҳифаи каталогро кушод ва фавран ба саҳифаи дигар рафт. Дархост барои 100 мол ба ҳар ҳол зеркашӣ ва таҷзия мешавад — трафик ва батарея беҳуда сарф мешавад (дар интернети мобилӣ дар маршрутка ин эҳсос мешавад).
Мушкили 2. Мусобиқаи дархостҳо (race condition)
Корбар «с»-ро навишт — дархости №1 рафт. Баъд аз 50 мс «са»-ро анҷом дод — дархости №2 рафт. Сервер дархостҳоро бо вақти гуногун коркард мекунад: ҷавоб ба якум (натиҷаҳояш бисёр) баъд аз 400 мс омад, ба дуюм — баъд аз 100 мс. Дар экран чӣ мешавад?
Хато: ҷавоби кӯҳнашуда навро пок мекунад · Ctrl + Enter ↺ Аз нав ▶ Иҷро кардан
// ↓ «сервер»: ҷустуҷӯи суст дар меню — тағйир надиҳед
const DISHES = ["Оши палав" , "Самбуса с мясом" , "Самбуса с тыквой" , "Курутоб" , "Шакароб" , "Нон" ];
function fakeSearch (query, { signal, ms = 200 } = {}) {
return new Promise ((resolve, reject) => {
if (signal && signal.aborted) return reject (signal.reason);
const timer = setTimeout (() => {
if (query === "ошибка" ) return reject (new Error ("Нет соединения с сервером" ));
resolve (DISHES.filter ((d) => d.toLowerCase ().includes (query.toLowerCase ())));
}, ms);
if (signal) {
signal.addEventListener ("abort" , () => {
clearTimeout (timer);
reject (signal.reason);
});
}
});
}
let shown = "" ;
function onType (query, ms) {
fakeSearch (query, { ms }).then (() => {
shown = query; // ҷавобро «мекашем»
console.log (`пришёл ответ на «${query}»` );
});
}
onType ("с" , 400 ); // дархости кӯҳна — суст
setTimeout (() => onType ("са" , 100 ), 50 ); // дархости нав — тез
setTimeout (() => console.log (`на экране: «${shown}»` ), 600 );
Дар экран «с» монд — ҳарчанд дар майдон аллакай «са» навишта шудааст. Ҷавоб ба дархости нав аввал омад, баъд ҷавоби дертар омадаи дархости кӯҳна онро пок кард. Корбар натиҷаҳоро на барои он чи навишт, мебинад. Хато ноустувор аст: дар хонаи шумо, бо интернети тез, вай зоҳир намешавад, назди муштарӣ дар кӯҳҳо — ҳатман зоҳир мешавад.
Қиёс аз ҳаёт
Шумо палав фармоиш додед, баъд аз як дақиқа фикратон гашт ва занг задед: «Беҳтараш самбуса». Агар дар чойхона фармоиши аввалро бекор накунанд, ба назди шумо ҳарду курйер меоянд — ва палав, шояд, пештар ё дертар аз самбуса. Дуруст — занг задан ва фармоиши аввалро бекор кардан . Маҳз инро AbortController мекунад: вай ба дархост «занг мезанад» ва мегӯяд «истед».
Чӣ тавр кор мекунад: controller ва signal
AbortController «Пулт»: объекте, ки тугмаи abort() дорад. Чунин сохта мешавад: new AbortController().
controller.signal «Симча»-и пулт: сигнале, ки ба дархост медиҳем. Дархост онро гӯш мекунад ва вақте тугма пахш шуд, истод.
controller.abort() Тугма пахш шуд — дархост бекор шуд, промиси он бо хатои бо номи AbortError рад мешавад .
Бекор кардани дархост: «сервер» signal дорад · Ctrl + Enter ↺ Аз нав ▶ Иҷро кардан
// ↓ «сервер»: ҷустуҷӯи суст дар меню — тағйир надиҳед
const DISHES = ["Оши палав" , "Самбуса с мясом" , "Самбуса с тыквой" , "Курутоб" , "Шакароб" , "Нон" ];
function fakeSearch (query, { signal, ms = 200 } = {}) {
return new Promise ((resolve, reject) => {
if (signal && signal.aborted) return reject (signal.reason);
const timer = setTimeout (() => {
if (query === "ошибка" ) return reject (new Error ("Нет соединения с сервером" ));
resolve (DISHES.filter ((d) => d.toLowerCase ().includes (query.toLowerCase ())));
}, ms);
if (signal) {
signal.addEventListener ("abort" , () => {
clearTimeout (timer);
reject (signal.reason);
});
}
});
}
const controller = new AbortController ();
// дархост барои 500 мс, ба он «пулт» пайваст аст
fakeSearch ("самбуса" , { signal: controller.signal, ms: 500 })
.then ((items) => console.log ("получили" , items.length))
.catch ((err) => console.log ("ошибка:" , err.name));
// баъд аз 100 мс корбар «Бекор»-ро пахш кард
setTimeout (() => {
console.log ("нажали abort()" );
controller.abort ();
}, 100 );
Дархосте, ки бояд 500 мс мерафт, баъд аз 100 истод. Ҷавоб наомад — ба ҷои он catch бо err.name === "AbortError" кор кард.
fetch-и воқеӣ signal-ро мефаҳмад
Ҷойгузини мо мисли fetch-и воқеӣ сохта шудааст: сигнал ҳамчун аргументи дуюм, дар объекти танзимот дода мешавад. Агар abort()-ро фавран даъват кунем, дархост ҳатто зеркаширо оғоз намекунад:
fetch-и воқеӣ + abort() · Ctrl + Enter ↺ Аз нав ▶ Иҷро кардан
const controller = new AbortController ();
const request = fetch ("https://dummyjson.com/products?limit=100" , {
signal: controller.signal,
});
controller.abort (); // фавран бекор мекунем
request.catch ((err) => console.log ("fetch отменён:" , err.name));
Дарозтар тоб намеорад — таймаут бо як сатр
Аксар вақт на бекор кардани дастӣ, балки интизор шудани на дарозтар аз N миллисония лозим мешавад. Барои ин функсияи тайёри AbortSignal.timeout(мс) ҳаст: вай худаш баъд аз вақти додашуда «abort»-ро пахш мекунад. Хато ҳангоми ин AbortError не, балки TimeoutError ном дорад — ба ин тартиб шумо «мо бекор кардем»-ро аз «интизор нашудем» фарқ мекунед.
Таймаут: на зиёда аз 1 мс интизор мешавем (интернет лозим аст) · Ctrl + Enter ↺ Аз нав ▶ Иҷро кардан
fetch ("https://dummyjson.com/products?limit=100" , { signal: AbortSignal.timeout (1 ) })
.then (() => console.log ("успели" ))
.catch ((err) => console.log ("ошибка:" , err.name));
Намуна: «ҳар дархости нав дархости гузаштаро бекор мекунад»
Маҳз ҳамин тавр мусобиқаи аввали қисмро ислоҳ кардан мумкин аст. Контроллерро дар тағйирёбандаи берун аз функсия нигоҳ медорем; пеш аз дархости нав кӯҳнаро бекор мекунем ва контроллери нав месозем.
Ислоҳ: дархости қаблиро бекор мекунем · Ctrl + Enter ↺ Аз нав ▶ Иҷро кардан
// ↓ «сервер»: ҷустуҷӯи суст дар меню — тағйир надиҳед
const DISHES = ["Оши палав" , "Самбуса с мясом" , "Самбуса с тыквой" , "Курутоб" , "Шакароб" , "Нон" ];
function fakeSearch (query, { signal, ms = 200 } = {}) {
return new Promise ((resolve, reject) => {
if (signal && signal.aborted) return reject (signal.reason);
const timer = setTimeout (() => {
if (query === "ошибка" ) return reject (new Error ("Нет соединения с сервером" ));
resolve (DISHES.filter ((d) => d.toLowerCase ().includes (query.toLowerCase ())));
}, ms);
if (signal) {
signal.addEventListener ("abort" , () => {
clearTimeout (timer);
reject (signal.reason);
});
}
});
}
let shown = "" ;
let controller = null ; // контроллери дархости охирин
function onType (query, ms) {
if (controller) controller.abort (); // 1. дархости гузаштаро бекор мекунем (агар меравад)
controller = new AbortController (); // 2. «пулт»-и нав месозем
fakeSearch (query, { signal: controller.signal, ms })
.then (() => {
shown = query;
console.log (`пришёл ответ на «${query}»` );
})
.catch ((err) => {
if (err.name !== "AbortError" ) console.log ("ошибка:" , err.message); // 3.
});
}
onType ("с" , 400 );
setTimeout (() => onType ("са" , 100 ), 50 );
setTimeout (() => console.log (`на экране: «${shown}»` ), 600 );
Як «пулт» барои як дархост. Контроллер якдафъаина аст: баъд аз abort() вай то абад «вайрон» мешавад, барои ҳамин барои ҳар дархост нав месозем.
Дар кӯҳна abort()-ро даъват мекунем пеш аз сохтани нав — дар ҳамин лаҳза дархости кӯҳна ҳанӯз дар роҳ буда метавонад.
Дар catch AbortError-ро хомӯшона мегузаронем. Бекор кардан — хатои сервер нест, ва ба корбар бо матни сурхи «Хато!» бо сабаби он ки ҳарфе илова кард, тарс додан мумкин нест.
Хатои маъмул
AbortError-ро коркард накардан. Дархости бекоршуда промисро рад мекунад ва агар дар catch оддӣ «хаторо нишон деҳ» навишта шуда бошад — дар экран як лаҳза «Ошибка: The user aborted a request» милтик мезанад. Ҳамеша err.name-ро санҷед ва барои "AbortError" хомӯшона бароед.
Роҳи дуюм ҳам ҳаст
Дархостро бекор накарда, оддӣ ҷавоби кӯҳнашударо нодида гирифтан мумкин аст: ҳисобкунаки requestId сохтан, рақами дархости худро ба ёд гирифтан ва ҳангоми ҷавоб санҷидан, ки вай ҳанӯз охирин аст. Ин оддитар аст, аммо дархост ба ҳар ҳол ба сервер мерасад ва трафик сарф мекунад. AbortController — роҳи «софдилона»: дархост воқеан қатъ мешавад.
Муфассалтар хонед: learn.javascript.ru: Fetch: қатъи дархост · MDN: AbortController
Амал: мустаҳкам мекунем
«Сервер» аллакай ба ҳар вазифа гузошта шудааст. Вай signal-ро мефаҳмад ва ҳангоми бекор кардан промисро бо ҳамон signal.reason рад мекунад.
ВАЗИФАИ 5.1 Корбар аз саҳифа баромад осон
Дархост 500 мс меравад, аммо баъд аз 100 мс корбар меравад. Бекор карданро анҷом диҳед: дар дохили setTimeout баъд аз сатри «пользователь ушёл» дархостро бекор кунед.
Таҳрир кардан мумкин аст · Ctrl + Enter ↺ Аз нав ▶ Иҷро кардан
// ↓ «сервер»: ҷустуҷӯи суст дар меню — тағйир надиҳед
const DISHES = ["Оши палав" , "Самбуса с мясом" , "Самбуса с тыквой" , "Курутоб" , "Шакароб" , "Нон" ];
function fakeSearch (query, { signal, ms = 200 } = {}) {
return new Promise ((resolve, reject) => {
if (signal && signal.aborted) return reject (signal.reason);
const timer = setTimeout (() => {
if (query === "ошибка" ) return reject (new Error ("Нет соединения с сервером" ));
resolve (DISHES.filter ((d) => d.toLowerCase ().includes (query.toLowerCase ())));
}, ms);
if (signal) {
signal.addEventListener ("abort" , () => {
clearTimeout (timer);
reject (signal.reason);
});
}
});
}
const controller = new AbortController ();
fakeSearch ("самбуса" , { signal: controller.signal, ms: 500 })
.then ((r) => console.log ("получили" , r.length))
.catch ((e) => console.log ("ошибка:" , e.name));
setTimeout (() => {
console.log ("пользователь ушёл" );
// дархостро дар ин ҷо бекор кунед
}, 100 );
Натиҷаи интизоршаванда пользователь ушёл
ошибка: AbortError
Ишора 1 Контроллери лозимӣ аллакай сохта шудааст — вай controller ном дорад ва тавассути signal ба дархост пайваст аст.
Ишора 2 Як сатр: controller.abort();
○ Ҳалшуда қайд кардан 💬 Дар бораи ин вазифа пурсидан
ВАЗИФАИ 5.2 Мусобиқаи дархостҳо миёна
Аввал пешгӯӣ кунед , ки коди поён чӣ чоп мекунад (ба қисми «Мушкили 2» нигаред), баъд иҷро кунед. Баъд onType-ро чунон ислоҳ кунед, ки дархости кӯҳна бекор шавад ва ҷавоб ба он пайдо нашавад. Хатои бекоркуниро бояд хомӯшона гузаронд.
Таҳрир кардан мумкин аст · Ctrl + Enter ↺ Аз нав ▶ Иҷро кардан
// ↓ «сервер»: ҷустуҷӯи суст дар меню — тағйир надиҳед
const DISHES = ["Оши палав" , "Самбуса с мясом" , "Самбуса с тыквой" , "Курутоб" , "Шакароб" , "Нон" ];
function fakeSearch (query, { signal, ms = 200 } = {}) {
return new Promise ((resolve, reject) => {
if (signal && signal.aborted) return reject (signal.reason);
const timer = setTimeout (() => {
if (query === "ошибка" ) return reject (new Error ("Нет соединения с сервером" ));
resolve (DISHES.filter ((d) => d.toLowerCase ().includes (query.toLowerCase ())));
}, ms);
if (signal) {
signal.addEventListener ("abort" , () => {
clearTimeout (timer);
reject (signal.reason);
});
}
});
}
let shown = "" ;
function onType (q, ms) {
fakeSearch (q, { ms }).then (() => {
shown = q;
console.log (`пришёл ответ на «${q}»` );
});
}
onType ("с" , 400 );
setTimeout (() => onType ("са" , 100 ), 50 );
setTimeout (() => console.log (`на экране: «${shown}»` ), 600 );
Натиҷаи интизоршаванда пас аз ислоҳ пришёл ответ на «са»
на экране: «са»
Ишора 1 Дар берун аз функсия let controller = null;-ро созед. Дар аввали onType: агар controller аллакай бошад — аз он abort()-ро даъват кунед, баъд AbortController-и нав созед.
Ишора 2 signal: controller.signal-ро ба танзимоти fakeSearch диҳед ва ба промис .catch((e) => { if (e.name !== "AbortError") console.log("ошибка:", e.message); })-ро илова кунед.
○ Ҳалшуда қайд кардан 💬 Дар бораи ин вазифа пурсидан
ВАЗИФАИ 5.3 На дарозтар аз ҳадди муқаррар интизор шудан миёна
async function searchWithTimeout(query, limit, ms)-ро нависед: вай fakeSearch-ро бо сигнали AbortSignal.timeout(limit) ва таъхири ms даъват мекунад. Агар ҷавоб омад — сатри Найдено: N-ро бармегардонад. Агар таймаут кор кард (err.name === "TimeoutError") — Сервер отвечает слишком долго. Ҳар хатои дигар — Ошибка: текст.
Таҳрир кардан мумкин аст · Ctrl + Enter ↺ Аз нав ▶ Иҷро кардан
// ↓ «сервер»: ҷустуҷӯи суст дар меню — тағйир надиҳед
const DISHES = ["Оши палав" , "Самбуса с мясом" , "Самбуса с тыквой" , "Курутоб" , "Шакароб" , "Нон" ];
function fakeSearch (query, { signal, ms = 200 } = {}) {
return new Promise ((resolve, reject) => {
if (signal && signal.aborted) return reject (signal.reason);
const timer = setTimeout (() => {
if (query === "ошибка" ) return reject (new Error ("Нет соединения с сервером" ));
resolve (DISHES.filter ((d) => d.toLowerCase ().includes (query.toLowerCase ())));
}, ms);
if (signal) {
signal.addEventListener ("abort" , () => {
clearTimeout (timer);
reject (signal.reason);
});
}
});
}
// async function searchWithTimeout(query, limit, ms) { ... }
async function main () {
console.log (await searchWithTimeout ("самбуса" , 500 , 200 ));
console.log (await searchWithTimeout ("курутоб" , 100 , 400 ));
}
main ();
Натиҷаи интизоршаванда Найдено: 2
Сервер отвечает слишком долго
Ишора 1 Ба аргументи дуюми fakeSearch объект диҳед: { signal: AbortSignal.timeout(limit), ms }. Муроҷиат ба функсия — дар дохили try.
Ишора 2 Дар catch (err) аввал err.name === "TimeoutError"-ро санҷед ва сатри лозимиро баргардонед, вагарна `Ошибка: ${err.message}`-ро баргардонед.
○ Ҳалшуда қайд кардан 💬 Дар бораи ин вазифа пурсидан
ВАЗИФАИ 5.4 createSearcher: ҷустуҷӯе, ки худаш худро бекор мекунад душвор
Функсияи createSearcher()-ро нависед, ки функсияи search(query)-ро бармегардонад. Ҳар даъвати нав дархости қаблиро бекор мекунад . Даъвати бекоршуда null бармегардонад (на ин ки хато мепартояд), муваффақ — массиви хӯрокҳоро. Хатоҳои дигар минбаъд партофта мешаванд.
Таҳрир кардан мумкин аст · Ctrl + Enter ↺ Аз нав ▶ Иҷро кардан
// ↓ «сервер»: ҷустуҷӯи суст дар меню — тағйир надиҳед
const DISHES = ["Оши палав" , "Самбуса с мясом" , "Самбуса с тыквой" , "Курутоб" , "Шакароб" , "Нон" ];
function fakeSearch (query, { signal, ms = 200 } = {}) {
return new Promise ((resolve, reject) => {
if (signal && signal.aborted) return reject (signal.reason);
const timer = setTimeout (() => {
if (query === "ошибка" ) return reject (new Error ("Нет соединения с сервером" ));
resolve (DISHES.filter ((d) => d.toLowerCase ().includes (query.toLowerCase ())));
}, ms);
if (signal) {
signal.addEventListener ("abort" , () => {
clearTimeout (timer);
reject (signal.reason);
});
}
});
}
// function createSearcher() { ... }
const search = createSearcher ();
const p1 = search ("самбуса" );
const p2 = new Promise ((resolve) => setTimeout (() => resolve (search ("курутоб" )), 50 ));
Promise.all ([p1, p2]).then (([a, b]) => {
console.log ("первый:" , a === null ? "отменён" : a.join (", " ));
console.log ("второй:" , b === null ? "отменён" : b.join (", " ));
});
Натиҷаи интизоршаванда первый: отменён
второй: Курутоб
Ишора 1 Тағйирёбандаи controller дар closure зиндагӣ мекунад — дар createSearcher, то return. search-и баргардонидашаванда — функсияи async аст: ин тавр истифодаи try / catch осонтар аст.
Ишора 2 Дар search: гузаштаро бекор кардан (агар ҳаст) → контроллери нав → try { return await fakeSearch(query, { signal: controller.signal }); } catch (e) { if (e.name === "AbortError") return null; throw e; }. Бе await пеш аз fakeSearch хато ба catch намеафтад.
○ Ҳалшуда қайд кардан 💬 Дар бораи ин вазифа пурсидан
06 Муаллим чӣ нишон медиҳад: Cursor
То ҳол ЗС дар курси мо — ин чат аст: шумо савол менависед, матн мегиред, худатон кодро нусха карда ба файл мегузоред. Имрӯз муаллим сатҳи навбатиро нишон медиҳад — агенти ЗС дар дохили муҳаррири код . Ин намоиш дар экран аст: Cursor-ро ба компютерҳои худ насб кардан лозим нест. Муҳимтарин дар ин қисм — на «вой, чӣ тез», балки чӣ тавр ин натиҷаро санҷидан .
Қоидаҳои курс, ки имрӯз махсусан муҳиманд
1. Аввал 15 дақиқа худатон кӯшиш мекунед. 2. Промпт — аз рӯи формула: контекст → вазифа → маҳдудиятҳо → формати ҷавоб → санҷиш. 3. Ба лоиҳа коде гузоред, ки онро сатр ба сатр шарҳ дода наметавонед. 4. Агент тез ва бисёр менависад — пас санҷидан низ бояд зуд ва бисёр бошад. Масъулият барои код ҳамеша бар души шумост, на агент.
Чат ва агент — фарқ дар чист
Чати ЗС (ChatGPT, Claude, Gemini) Агенти ЗС дар муҳаррир (Cursor) Чӣ мебинад Танҳо он чиро, ки шумо ба равзана гузоштед Тамоми лоиҳа: худаш файлҳои лозимаро мекушояд ва меҷӯяд Чӣ мекунад Бо матн ҷавоб медиҳад Нақша пешниҳод мекунад ва якбора якчанд файлро ислоҳ мекунад; бо иҷозат фармонҳоро иҷро карда метавонад Кӣ код ворид мекунад Шумо — бо нусхабардорӣ Агент — шумо ислоҳҳоро мебинед ва қабул ё рад мекунед Хавфи асосӣ Ҷавобро насанҷидаанд «Ҳамаро қабул кун»-ро пахш карданд ва ислоҳҳоро дар панҷ файл нахонданд
Дар бораи интерфейс муҳим аст
Cursor — муҳаррирест дар асоси VS Code бо ЗС дар дохил, ва вай зуд рушд мекунад: номи ҳолатҳо ва тугмаҳо аз версия ба версия иваз мешавад. Барои ҳамин дар ин ҷо мо моҳият ва тартиби амалҳоро тавсиф мекунем, на «тугмаи сеюми тарафи ростро пахш кунед». Дар намоиш ба мантиқ назар кунед: агент чиро хонд, чиро пешниҳод кард, чиро тағйир дод.
Сенарияи намоиш
Муаллим барномаи хурдро аз дарсҳои гузашта — ҷустуҷӯ аз рӯи API бо рӯйхат ва пагинатсия — бар якчанд файл тақсимшуда мегирад: index.html, style.css, api.js (дархостҳо), ui.js (кашидан) ва main.js (ҳодисаҳо). Вазифа — маҳз ҳамон аст, ки имрӯз бо даст ҳал мекунем: ҳолатҳои боркунӣ, холигӣ ва хато, debounce ва бекор кардани дархостҳоро илова кардан.
Нусхаро нигоҳ медорем. Пеш аз демо ҷузвдони лоиҳаро нусха мекунанд (Git-ро дар дарси 19 меомӯзем, ҳоло — нусхаи оддӣ). Агар агент вайрон кунад, ҳамеша баргаштан мумкин аст.
Вазифаро аз рӯи формула мегузорем. Промпт бо овози баланд хонда мешавад (вай дар поён аст) — ҳар порчаи он барои қисми худи формула масъул аст.
Мебинем, ки агент чиро мехонад. Агент «тахмин» намекунад: вай файлҳои лоиҳаро мекушояд. Диққат диҳед, кадом файлҳоро кушод — оё лозимаро гузаронд.
Нақшаро мехонем — пеш аз ислоҳҳо. Аз агент хоҳиш мекунем, ки аввал нақша нависад: кадом файлҳо ва чиро тағйир медиҳад. Аллакай дар ин қадам дида мешавад, ки вазифаро фаҳмид ё не.
Ислоҳҳоро файл ба файл баррасӣ мекунем. Дар муҳаррир фарқ дида мешавад: чӣ илова шуд, чӣ нест шуд. Қабул мекунем яктоӣ-яктоӣ , на «ҳама якбора».
Иҷро мекунем ва кӯшиш мекунем вайрон кунем. Дар ҷустуҷӯ зуд менависем; дар DevTools дар ҷадвали Network шабакаи сустро ва ҳолати Offline-ро фаъол мекунем; дархостеро меҷӯем, ки дар API нест.
Фаромӯшшударо меёбем. Қариб ҳамеша чизе мемонад: саҳифаро ба аввал барқарор карданро ҳангоми дархости нав фаромӯш карданд, AbortError-ро гузарониданд, console.log-ро монданд. Инро промпти навбатӣ ё бо даст ислоҳ мекунанд.
Промпт барои демо
Диққат диҳед, вай чӣ тавр сохта шудааст: аввал контекст, баъд вазифа, баъд маҳдудиятҳо, сипас чиро нишон додан ва чӣ тавр санҷидан. Бо варианти «бад» муқоиса кунед:
ба лоиҳаи ман боркунӣ ва ҷустуҷӯро бо таъхир илова кунНусха бардоштан
Контекст: барномаи таълимӣ бо JS-и пок бе китобхонаҳо — ҷустуҷӯи хӯрокҳо аз рӯи API бо пагинатсия. Файлҳо: index.html, style.css, api.js (fetch), ui.js (рӯйхатро мекашад), main.js (ҳодисаҳо). Вазифа: 1) се ҳолатро илова кун — боркунӣ (skeleton аз се блоки хокистарӣ), холӣ («Ничего не найдено») ва хато (матни фаҳмо ва тугмаи «Повторить»); 2) debounce 400 мс ба вурудот дар сатри ҷустуҷӯ илова кун; 3) ҳангоми дархости нав қаблиро тавассути AbortController бекор кун ва AbortError-ро хомӯшона нодида гир; 4) ҳангоми дархости ҷустуҷӯи нав саҳифаро ба аввал барқарор кун. Маҳдудиятҳо: мантиқи пагинатсия ва номи синфҳои CSS-и мавҷударо ламс накун, китобхона илова накун, файлҳоеро, ки барои ин вазифа лозим нестанд, тағйир надеҳ. Формат: аввал нақша нависед — кадом файлҳоро ва барои чӣ тағйир медиҳӣ; баъд ислоҳҳоро ворид кун ва файлҳои тағйирёфтаро номбар кун. Санҷиш: бинвис, ки ман чӣ тавр ҳар яке аз чор тағйиротро дастӣ санҷам.Нусха бардоштан
Дар вақти намоиш ба чӣ нигоҳ кардан лозим
Ин саволҳоро дар хотир доред, ҳангоме ки ба экран менигаред, — ва онҳоро бо овози баланд диҳед:
Чанд файл тағйир ёфтааст ва оё ҳамаи онҳо лозиманд? Ислоҳ дар style.css бамаврид аст (skeleton). Аммо тағйирот дар функсияҳои бегона, ки мо ламс кардани онҳоро нахостем, — сабаби истодан аст.
Оё тамоми diff хонда шудааст? Сурх — нест шуд. Оё мантиқи корӣ (масалан, пагинатсия) дар «рефакторинг» нопадид нашуд?
Оё чизи ихтироъшуда нест? Майдони JSON, ки дар ҷавоби воқеии API нест, ё функсияе, ки дар браузер нест. Бо ҷавоби воқеӣ дар ҷадвали Network муқоиса мекунем.
Оё ҳар чор ҳолат коркард шудаанд? Махсусан омехтагии «холӣ» ва «хато» — ЗС онҳоро зуд-зуд омехта мекунад.
Оё AbortError дар catch ҳаст? Бе он дар экран «хато» бо ҳар ҳарфи нав милтик мезанад.
Оё ман метавонам ҳар сатрро шарҳ диҳам? Агар не — кодро қабул намекунем (қоидаи курс №3).
Хавфҳо: чӣ метавонад хато равад
Одатҳои хатарнок
«Ҳамаро қабул кун»-ро бе хондани ислоҳҳо пахш кардан
Бе маҳдудият «ҳамаро ислоҳ кун» хоҳиш кардан: агент он чиро, ки кор мекард, аз нав менависад
Бовар кардан, ки «агент худаш санҷид» — иҷро ва санҷиш бар души шумо мемонанд
Ба агент сирҳо додан: калидҳои API, паролҳо, файлҳо бо маълумоти шахсӣ (.env)
Хондани кодро бас кардан — бо мурури замон малака гум мешавад
Одатҳои солим
Вазифаи хурд — ислоҳҳои хурд — санҷиш
Ҳамеша пеш аз ислоҳҳо нақша ва баъд аз онҳо рӯйхати файлҳои тағйирёфтаро хоҳиш кардан
Равшан навиштан, ки чиро ламс кардан мумкин нест
То оғози кори агент нусхаи лоиҳаро нигоҳ доштан (баъдтар — коммит дар Git)
Дар браузер санҷидан: зуд, суст, офлайн, холӣ
Махфият
Коде, ки шумо ба агент нишон медиҳед, ба сервери хидмати ЗС фиристода мешавад. Барои лоиҳаҳои таълимӣ ин муқаррарӣ аст, аммо коди корӣ-и фармоишгар, паролҳо ва калидҳоро бе иҷозат ба он ҷо фиристодан мумкин нест. Одат кунед, ки дар ин бора пешакӣ фикр кунед: дар амал ин масъалаи эътимод ва гоҳо — қонун аст.
Ва шумо — тавассути чати ЗС: ҳуҷҷатгузории API-ро таҳлил мекунем
Ҳамин ҳозир шумо метавонед ЗС-ро дар ҷое истифода баред, ки вай махсусан судманд аст: барои фаҳмидани ҳуҷҷатгузории бегона. Формула ҳамон:
чӣ тавр аз ин api маълумот гирифтанНусха бардоштан
Инак ҳуҷҷатгузории API-и ҷустуҷӯи мол: [матн ё истинодро гузоред]. Ба ман лозим аст 5 натиҷаи аввалро аз рӯи дархост аз майдони вуруд гирам ва ҳолатеро, ки чизе ёфт нашуд, коркард кунам. Кадом эндпойнт, кадом параметрҳо, кадом мисоли дархост бо fetch? JS-и пок, бе китобхонаҳо. Чунин ҷавоб деҳ: 1) ҷадвали параметрҳо; 2) мисоли код; 3) дар ҷавоби воқеӣ чиро санҷидан лозим, то боварӣ ҳосил шавад, ки майдонҳо дуруст номгузорӣ шудаанд.Нусха бардоштан
Санҷиши ҷавоби ЗС: бо API-и воқеӣ муқоиса мекунем
Суроғаи дархостро рост дар браузер кушоед (масалан, https://dummyjson.com/products/search?q=phone&limit=5) ё ҷавобро дар ҷадвали Network бинед. Оё номи майдонҳо (products, total) бо навиштаи ЗС мувофиқат мекунанд? Агар не — ЗС ихтироъ кардааст ё омехта кардааст; мо танҳо ба ҷавоби воқеии сервер бовар мекунем.
Амал: баррасии кори «агент»
Дар поён — код, чунон ки агент аз рӯи промпти мо навишта метавонист. Мо дар кори воқеӣ ҳамин тавр мекунем: аз сифр намеорем, балки кори бегонаро месанҷем ва ислоҳ мекунем .
ВАЗИФАИ 6.1 Баррасии коди агент: се хато душвор
Дар коди поён се хато ҳаст; аз сабаби онҳо ҷустуҷӯ дархостҳои зиёдатӣ мегирад ва ҳолатҳои нодурустро нишон медиҳад. Ҷадвали «пахшҳо» дар охир: «к», «ку», «кур» — силсилаи зуд; баъд «самбуса», ҳангоме ки дархости қаблӣ ҳанӯз меравад; баъд «борщ» (дар меню чунин нест). Кодро иҷро кунед, ба бесарусомонӣ дар консол нигаред, хатоҳоро ёбед ва ислоҳ кунед. Баъд аз ислоҳ ҳолатҳо бояд маҳз бо ин тартиб раванд: боркунӣ, боркунӣ, муваффақият, боркунӣ, холӣ.
Таҳрир кардан мумкин аст · Ctrl + Enter ↺ Аз нав ▶ Иҷро кардан
// ↓ «сервер»: ҷустуҷӯи суст дар меню — тағйир надиҳед
const DISHES = ["Оши палав" , "Самбуса с мясом" , "Самбуса с тыквой" , "Курутоб" , "Шакароб" , "Нон" ];
function fakeSearch (query, { signal, ms = 200 } = {}) {
return new Promise ((resolve, reject) => {
if (signal && signal.aborted) return reject (signal.reason);
const timer = setTimeout (() => {
if (query === "ошибка" ) return reject (new Error ("Нет соединения с сервером" ));
resolve (DISHES.filter ((d) => d.toLowerCase ().includes (query.toLowerCase ())));
}, ms);
if (signal) {
signal.addEventListener ("abort" , () => {
clearTimeout (timer);
reject (signal.reason);
});
}
});
}
let state = { status: "idle" , data: [], error: null };
let timer = null ;
let controller = null ;
function setState (patch) {
state = { ... state, ... patch };
console.log (`состояние: ${state.status}` );
}
async function runSearch (query) {
controller = new AbortController ();
setState ({ status: "loading" });
try {
const items = await fakeSearch (query, { signal: controller.signal });
setState ({ status: "success" , data: items });
} catch (e) {
setState ({ status: "error" , error: e.message });
}
}
function onInput (query) {
timer = setTimeout (() => runSearch (query), 50 );
}
[[0 , "к" ], [20 , "ку" ], [40 , "кур" ], [150 , "самбуса" ], [550 , "борщ" ]].forEach (([at, q]) =>
setTimeout (() => onInput (q), at)
);
Натиҷаи интизоршаванда состояние: loading
состояние: loading
состояние: success
состояние: loading
состояние: empty
Ишора 1 Се нуқсон: (1) дар onInput clearTimeout нест — ин debounce нест; (2) runSearch дархости қаблиро бекор намекунад; (3) ҷавоби холӣ ба ҷои empty статуси success мегирад.
Ишора 2 Ҳангоми ислоҳи нуқсони дуюм шумо бо чорум рӯ ба рӯ мешавед: дархости бекоршуда ба catch меафтад ва error-ро нишон медиҳад. Дар catch бояд аввал e.name === "AbortError"-ро санҷед ва тавассути return бе ҳолат бароед.
○ Ҳалшуда қайд кардан 💬 Дар бораи ин вазифа пурсидан
07 Амал дар дарс: се ҳолат ва debounce
Ҳамаро дар барномаи хурди «Менюи чойхона» ҷамъ мекунем: сатри ҷустуҷӯ ва рӯйхати хӯрокҳо. Ба ҷои API-и воқеӣ дар ин ҷо ҷойгузини шиноси fakeSearch ҳаст, то ҳама чиз барои ҳама якхела ва бе интернет кор кунад. Шумо дар ду марҳила кор мекунед: аввал ҳолатҳо , баъд debounce ва бекор кардан .
Марҳилаи 1. Ҳолатҳо. Як объекти state ва функсияи render, ки аз рӯи state.status мекашад: ишора, skeleton, кортҳо, «холӣ» ё хато бо тугмаи «Такрор кардан». Функсияи load статусро мегузаронад: loading → success / empty / error.
Марҳилаи 2. Debounce. debounce-ро менависем, ба input мепайвандем: дархост танҳо баъд аз танаффус меравад.
Марҳилаи 3. Бекор кардан. Пеш аз дархости нав кӯҳнаро бекор мекунем, AbortError-ро хомӯшона мегузаронем.
Марҳилаи 4. Дар барномаи худ. Ҳамонро ба лоиҳаи дарсҳои 14–15 мегузаронем — дар поён нақша ҳаст.
Амал: менюи чойхона
Сатрҳои санҷишии консол тасдиқ мекунанд, ки ҳар марҳила кор мекунад. Онҳо худашон «тугмаҳо»-ро пахш мекунанд, барои ҳамин натиҷа барои ҳама якхела аст.
ВАЗИФАИ 7.1 Менюи чойхона: чор ҳолат миёна
render ва load-ро анҷом диҳед. Барои ҳар статус тамғагузории худӣ лозим аст: idle — <p class="hint">Начните вводить название блюда</p>; loading — се div.skeleton; success — барои ҳар хӯрок div.dish; empty — <p class="empty">Ничего не найдено</p>; error — <p class="error"> бо матни хато ва <button class="retry">Повторить</button>. Функсияи load(query) loading-ро мемонад (ва фавран аз нав мекашад), баъд — аз рӯи натиҷаи дархост — success, empty ё error ва боз аз нав мекашад.
Менюи чойхона: ҳолатҳо · Ctrl + Enter ↺ Аз нав ▶ Иҷро кардан
index.html style.css script.js
< h3 class ="title" > Меню чайханы</ h3 >
< input id ="q" class ="search" placeholder ="Что ищем?" autocomplete ="off" >
< div id ="result" class ="result" > </ div >
.title { margin : 0 0 10px ; font-size : 20px ; }
.search { width : 100 %; box-sizing : border-box; padding : 10px 12px ; border : 2px solid #ccd3df ; border-radius : 10px ; font-size : 16px ; margin-bottom : 12px ; }
.result { display : grid; gap : 8px ; }
.skeleton { height : 42px ; border-radius : 10px ; background : linear-gradient(90deg , #e6e9ef 25 %, #f4f6fa 37 %, #e6e9ef 63 %); background-size : 400 % 100 %; animation : shimmer 1.2s ease infinite; }
@keyframes shimmer { 0 % { background-position : 100 % 0 ; } 100 % { background-position : 0 0 ; } }
.dish { padding : 10px 14px ; border : 2px solid #ccd3df ; border-radius : 10px ; background : #fff ; font-weight : 700 ; }
.hint, .empty { color : #6b7488 ; margin : 0 ; }
.error { color : #c0392b ; font-weight : 700 ; margin : 0 ; }
.retry { justify-self : start; border : 2px solid #1c2230 ; background : #fff ; border-radius : 8px ; padding : 6px 12px ; cursor : pointer; font-weight : 700 ; }
// ↓ «сервер»: ҷустуҷӯи суст дар меню — тағйир надиҳед
const DISHES = ["Оши палав" , "Самбуса с мясом" , "Самбуса с тыквой" , "Курутоб" , "Шакароб" , "Нон" ];
function fakeSearch (query, { signal, ms = 200 } = {}) {
return new Promise ((resolve, reject) => {
if (signal && signal.aborted) return reject (signal.reason);
const timer = setTimeout (() => {
if (query === "ошибка" ) return reject (new Error ("Нет соединения с сервером" ));
resolve (DISHES.filter ((d) => d.toLowerCase ().includes (query.toLowerCase ())));
}, ms);
if (signal) {
signal.addEventListener ("abort" , () => {
clearTimeout (timer);
reject (signal.reason);
});
}
});
}
const result = document.querySelector ("#result" );
let state = { status: "idle" , items: [], error: null };
function render () {
result.dataset.state = state.status;
result.replaceChildren ();
// TODO 1: result-ро вобаста ба state.status кашед
}
async function load (query) {
// TODO 2: loading -> дархост -> success / empty / error
// баъд аз ҳар тағйири state render()-ро даъват кунед
}
render ();
// --- санҷиш (нест накунед) ---
async function check () {
const request = load ("самбуса" );
console.log (result.dataset.state, result.children.length);
await request;
console.log (result.dataset.state, result.children.length);
await load ("борщ" );
console.log (result.dataset.state, result.textContent.trim ());
await load ("ошибка" );
console.log (result.dataset.state, !!result.querySelector (".retry" ));
}
check ();
Пешнамоиш Пӯшонидан
Барои дидани натиҷа «Иҷро кардан»-ро пахш кунед.
Натиҷаи интизоршаванда loading 3
success 2
empty Ничего не найдено
error true
Ишора 1 Дар render — занҷири if / else if аз рӯи state.status. Шохаи loading: давра барои се қадам бо document.createElement("div") ва className = "skeleton". Шохаи success: state.items.map(...) ва result.append(...cards).
Ишора 2 Дар load: state = { status: "loading", items: [], error: null }; render(); — баъд try { const items = await fakeSearch(query); ... } catch (err) { ... }. Статуси "empty"-ро мемонед, агар items.length === 0 бошад. render()-ро баъд аз ҳар тағйири state фаромӯш накунед.
○ Ҳалшуда қайд кардан 💬 Дар бораи ин вазифа пурсидан
ВАЗИФАИ 7.2 Менюи чойхона: debounce ва бекор кардан душвор
Ҳолатҳо аллакай омодаанд (render ва load аз вазифаи гузашта). Чор ҷойро анҷом диҳед: (1) функсияи debounce(fn, wait); (2) дар аввали load — бекор кардани дархости қаблӣ ва сохтани контроллери нав; (3) дар catch — баромадани хомӯшона ҳангоми AbortError; (4) шунавандаи ҳодисаи input дар майдони ҷустуҷӯ, ки бо debounce 100 мс load(input.value.trim())-ро даъват мекунад. Санҷиш худаш матнро «менависад»: силсилаи зуди «с», «са», «сам», баъд «кур» ва фавран баъд — «курутоб». Дархостҳои зиёдатӣ бояд қатъ шаванд, ҷавоби кӯҳнашуда — кашида нашавад.
Менюи чойхона: debounce ва abort · Ctrl + Enter ↺ Аз нав ▶ Иҷро кардан
index.html style.css script.js
< h3 class ="title" > Меню чайханы</ h3 >
< input id ="q" class ="search" placeholder ="Что ищем?" autocomplete ="off" >
< div id ="result" class ="result" > </ div >
.title { margin : 0 0 10px ; font-size : 20px ; }
.search { width : 100 %; box-sizing : border-box; padding : 10px 12px ; border : 2px solid #ccd3df ; border-radius : 10px ; font-size : 16px ; margin-bottom : 12px ; }
.result { display : grid; gap : 8px ; }
.skeleton { height : 42px ; border-radius : 10px ; background : linear-gradient(90deg , #e6e9ef 25 %, #f4f6fa 37 %, #e6e9ef 63 %); background-size : 400 % 100 %; animation : shimmer 1.2s ease infinite; }
@keyframes shimmer { 0 % { background-position : 100 % 0 ; } 100 % { background-position : 0 0 ; } }
.dish { padding : 10px 14px ; border : 2px solid #ccd3df ; border-radius : 10px ; background : #fff ; font-weight : 700 ; }
.hint, .empty { color : #6b7488 ; margin : 0 ; }
.error { color : #c0392b ; font-weight : 700 ; margin : 0 ; }
.retry { justify-self : start; border : 2px solid #1c2230 ; background : #fff ; border-radius : 8px ; padding : 6px 12px ; cursor : pointer; font-weight : 700 ; }
const DISHES = ["Оши палав" , "Самбуса с мясом" , "Самбуса с тыквой" , "Курутоб" , "Шакароб" , "Нон" ];
let requests = 0 ; // воқеан чанд дархост рафт
function fakeSearch (query, { signal, ms = 300 } = {}) {
requests++;
return new Promise ((resolve, reject) => {
if (signal && signal.aborted) return reject (signal.reason);
const timer = setTimeout (() => {
if (query === "ошибка" ) return reject (new Error ("Нет соединения с сервером" ));
resolve (DISHES.filter ((d) => d.toLowerCase ().includes (query.toLowerCase ())));
}, ms);
if (signal) {
signal.addEventListener ("abort" , () => {
clearTimeout (timer);
reject (signal.reason);
});
}
});
}
const input = document.querySelector ("#q" );
const result = document.querySelector ("#result" );
let state = { status: "idle" , items: [], error: null };
let controller = null ;
let successRenders = 0 ; // чанд маротиба муваффақиятро кашидем
function render () {
result.dataset.state = state.status;
result.replaceChildren ();
if (state.status === "idle" ) {
const p = document.createElement ("p" );
p.className = "hint" ;
p.textContent = "Начните вводить название блюда" ;
result.append (p);
} else if (state.status === "loading" ) {
for (let i = 0 ; i < 3 ; i++) {
const stub = document.createElement ("div" );
stub.className = "skeleton" ;
result.append (stub);
}
} else if (state.status === "success" ) {
successRenders++;
result.append (... state.items.map ((name) => {
const dish = document.createElement ("div" );
dish.className = "dish" ;
dish.textContent = name;
return dish;
}));
} else if (state.status === "empty" ) {
const p = document.createElement ("p" );
p.className = "empty" ;
p.textContent = "Ничего не найдено" ;
result.append (p);
} else {
const p = document.createElement ("p" );
p.className = "error" ;
p.textContent = state.error;
const retry = document.createElement ("button" );
retry.className = "retry" ;
retry.textContent = "Повторить" ;
result.append (p, retry);
}
}
// TODO 1: function debounce(fn, wait) { ... }
async function load (query) {
// TODO 2: агар controller аллакай бошад — онро бекор кунед; баъд нав созед
state = { status: "loading" , items: [], error: null };
render ();
try {
const items = await fakeSearch (query, { signal: controller.signal });
state = { status: items.length === 0 ? "empty" : "success" , items, error: null };
} catch (err) {
// TODO 3: агар err.name === "AbortError" — оддӣ аз функсия бароед
state = { status: "error" , items: [], error: err.message };
}
render ();
}
// TODO 4: input.addEventListener("input", ...) бо debounce 100 мс
render ();
// --- санҷиш (нест накунед) ---
function type (text) {
input.value = text;
input.dispatchEvent (new Event ("input" ));
}
type ("с" );
setTimeout (() => type ("са" ), 30 );
setTimeout (() => type ("сам" ), 60 );
setTimeout (() => console.log ("запросов:" , requests), 500 );
setTimeout (() => type ("кур" ), 550 );
setTimeout (() => type ("курутоб" ), 720 );
setTimeout (() => {
console.log ("запросов:" , requests);
console.log ("успешных отрисовок:" , successRenders);
console.log (result.dataset.state, result.children.length);
}, 1250 );
Пешнамоиш Пӯшонидан
Барои дидани натиҷа «Иҷро кардан»-ро пахш кунед.
Натиҷаи интизоршаванда запросов: 1
запросов: 3
успешных отрисовок: 2
success 1
Ишора 1 Се пахши пай дар пай («с», «са», «сам») бояд як дархост диҳад — пас debounce кор мекунад. Агар дар консол «запросов: 3» аллакай дар сатри аввал бошад — шунаванда load-ро бевосита, бе debounce даъват мекунад.
Ишора 2 Агар «успешных отрисовок» 3 баромад, на 2 — бекор кардан кор намекунад: ҷавоб ба «кур» кашида шуд, ҳарчанд корбар аллакай «курутоб»-ро ворид кардааст. Санҷед, ки abort() пеш аз сохтани контроллери нав даъват мешавад ва AbortError дар catch бо return анҷом меёбад.
○ Ҳалшуда қайд кардан 💬 Дар бораи ин вазифа пурсидан
Марҳилаи 4. Ба барномаи худ мегузаронем
Барномаи шумо аз дарсҳои 14–15 аллакай ҷустуҷӯ ва варақ задани саҳифаҳоро медонад. Ҳамонро қадам ба қадам ба он илова мекунем. Баъд аз ҳар қадам иҷро кунед ва санҷед:
state-ро созед бо майдонҳои status, items, error (ва page, агар пагинатсия дошта бошед).
Кашидани ҳамаро ба як функсияи render() баред , ки аз рӯи state.status тасвири лозимаро нишон медиҳад. Дар ҷои дигар DOM-ро ламс накунед.
Дархостро бипечонед : аввал loading, баъд success / empty / error. if (!res.ok) throw ...-ро фаромӯш накунед.
Skeleton : CSS-ро аз қисми 3 нусха кунед, шумораи лозимаи ҷойгузинҳоро дар ҳолати loading илова кунед.
Debounce : debounce-ро нусха кунед, коркардкунандаи input-ро бипечонед — танаффус 300–400 мс.
AbortController : signal-ро ба fetch-и худ диҳед, дархости гузаштаро бекор кунед, AbortError-ро гузаронед.
Дар DevTools санҷед (ҷадвали Network): шабакаи сустро (Slow 4G) ва ҳолати Offline-ро фаъол кунед — ҳар се ҳолат бояд зинда дида шавад.
Скелет барои барномаи худ (бо API-и воқеӣ, интернет лозим аст) · Ctrl + Enter ↺ Аз нав ▶ Иҷро кардан
let state = { status: "idle" , items: [], error: null };
let controller = null ;
async function load (query) {
if (controller) controller.abort ();
controller = new AbortController ();
state = { status: "loading" , items: [], error: null };
console.log ("состояние:" , state.status); // инҷо render() мешавад
try {
const url = `https://dummyjson.com/products/search?q=${encodeURIComponent(query)}&limit=5` ;
const res = await fetch (url, { signal: controller.signal });
if (!res.ok) throw new Error (`Сервер ответил кодом ${res.status}` );
const data = await res.json ();
state = { status: data.products.length ? "success" : "empty" , items: data.products, error: null };
} catch (err) {
if (err.name === "AbortError" ) return ; // бекор кардан — хато нест
state = { status: "error" , items: [], error: err.message };
}
console.log ("состояние:" , state.status, "| найдено:" , state.items.length);
}
load ("phone" );
Дар бораи пагинатсия фаромӯш накунед
Агар дар барнома саҳифаҳо бошанд, ҳангоми дархости ҷустуҷӯи нав ба саҳифаи аввал баргардед. Ҳангоми боркунӣ — тугмаҳои «Ба қафо» ва «Ба пеш»-ро ғайрифаъол кунед (disabled), вагарна корбар метавонад онҳоро якчанд бор пай дар пай пахш кунад.
08 Вазифаи хонагӣ
UX-и барномаро такмил диҳед: боркунӣ, натиҷаи холӣ, хатои шабака. Барномаи худро аз дарсҳои 14–15 (рӯйхат ё ҷустуҷӯ аз рӯи API-и воқеӣ) мегиред ва онро ба корбар мулоим мекунед.
Чӣ кор кардан лозим
Боркунӣ. То даме ки дархост меравад, skeleton нишон дода мешавад (3–6 ҷойгузин бо шакли лозимӣ). Дар контейнер — aria-busy.
Холӣ. Агар чизе ёфт нашуд — матни фаҳмо («Ничего не найдено по запросу «…»») ва ишора, ки чӣ кор кардан лозим.
Хато. Санҷиши res.ok; паёми инсонӣ (на «Failed to fetch») ва тугмаи «Такрор кардан», ки ҳамон дархостро такрор мекунад.
Debounce. Ҷустуҷӯи «дар парвоз» бо debounce 300–500 мс, ки шумо навиштаед (на аз китобхона).
Бекор кардан. Дархости нав қаблиро тавассути AbortController бекор мекунад; AbortError ҳамчун хато нишон дода намешавад.
Санҷиш дар DevTools. Се скриншот гиред: боркунӣ дар шабакаи суст, ҳолати Offline бо хато, натиҷаи холӣ.
(Бонус.) Ҳангоми дархости нав саҳифа ба аввал барқарор мешавад; тугмаҳои пагинатсия барои вақти боркунӣ ғайрифаъол мешаванд.
Чӣ супоридан лозим
Истинод ба репозиторий дар GitHub (ё ҷузвдони лоиҳа — чунон ки бо муаллим мувофиқа кунед).
Се скриншоти ҳолатҳо (боркунӣ, хато, холӣ).
Тавсифи кӯтоҳ дар 3–5 ҷумла: чӣ аз ҳама душвортар буд ва ЗС-ро дар куҷо истифода бурдед. Агар истифода бурда бошед — ҳар сатрро бояд шарҳ дода тавонед.
Чӣ тавр баҳо медиҳем (0–10)
Чӣ Холҳо Боркунӣ: skeleton ё нишондиҳии дигари фаҳмо, мазмун «ҷаст намезанад» 2 Холӣ ва хато: матнҳои гуногун, санҷиши res.ok, «Такрор кардан» кор мекунад 2 Debounce мустақилона навишта шудааст ва кор мекунад (як дархост барои силсилаи пахшҳо) 2 AbortController: дархости кӯҳна бекор мешавад, AbortError нишон дода намешавад 2 Скриншотҳо ва тавсиф; донишҷӯ код ва қарорҳоро дар ҳимоя шарҳ медиҳад 2
Пеш аз супоридан
Кӯшиш кунед барномаи худро вайрон кунед: зуд нависед, «Ёфтан»-ро бисёр бор пай дар пай пахш кунед, Offline-ро фаъол кунед, дархостеро ворид кунед, ки нест. Агар истодагарӣ кунад — тайёр аст.
✓ Лавҳаи ёддошти дарс
Чор ҳолат: loading (бор мекунем), error (вайрон шуд + «Такрор кардан»), empty (ҷавоб холӣ аст), success (маълумот). Плюс idle — оғоз. Як status ба ҷои бисёр булиҳо.
Тартиби санҷишҳо: аввал loading ва error, баъд «холӣ ё не». Вагарна ҳангоми боркунӣ «Ничего не найдено» нишон дода мешавад.
fetch ва хатоҳо: шабака нест → промис рад шуд (catch); сервер 404/500 ҷавоб дод → барои fetch муваффақият . Ҳамеша if (!res.ok) throw new Error(...).
Ба корбар — матни фаҳмо ва «Такрор кардан»; ҷузъиёти техникӣ — дар console.error.
Skeleton: блокҳои хокистарӣ бо ҳамон шакл + background-size: 400% ва аниматсияи background-position; aria-busy; prefers-reduced-motion; мазмунро пурра иваз кардан (replaceChildren).
debounce(fn, wait): let timer дар берун, дар дохил clearTimeout(timer); timer = setTimeout(() => fn(...args), wait). Танаффус барои ҷустуҷӯ — 300–500 мс. Debounce — барои ҷараёни ҳодисаҳост, на барои тугмаҳо.
AbortController: const c = new AbortController(); fetch(url, { signal: c.signal }); c.abort(); — промис бо AbortError рад мешавад. Контроллер якдафъаина аст; барои дархости нав — нав.
Мусобиқаи дархостҳо: дархости нав гузаштаро бекор мекунад; AbortError-ро дар catch хомӯшона мегузаронем. AbortSignal.timeout(мс) — таймаут (TimeoutError).
Агенти ЗС (Cursor): тамоми лоиҳаро мебинад, якчанд файлро ислоҳ мекунад. Пеш аз ислоҳҳо нақша мепурсем, diff-ро файл ба файл мехонем, «ҳамаро қабул кун»-ро намепахшем, дар браузер месанҷем (зуд, суст, офлайн, холӣ); сирҳоро ба агент намедиҳем.