Що повертає функція Fetch після виконання

Що повертає функція Fetch після виконання



Використання Fetch

Fetch API надає JavaScript для роботи з запитами та відповідями HTTP. Він також надає глобальний метод fetch() , який дозволяє легко та логічно отримувати ресурси через мережу асинхронно.

Подібна функціональність раніше досягалася за допомогою XMLHttpRequest. Fetch є найкращою альтернативою, яка може бути легко використана іншими технологіями, такими як Service Workers. Fetch також забезпечує єдине логічне місце визначення інших пов'язаних з HTTP понять, такі як CORS і розширення для HTTP.

Зверніть увагу, специфікація fetch відрізняється від jQuery.ajax() в основному в двох пунктах:

  • Promise, що повертається викликом fetch() не перейде в стан "відхилено" через відповідь HTTP, яка вважається помилкою, навіть якщо відповідь HTTP 404 або 500. Замість цього, він буде виконаний нормально (зі значенням false у статусі ok ) і буде відхилений тільки при збої мережі або якщо щось завадило запиту виконатися.
  • За замовчуванням fetch не буде надсилати або отримувати файли cookie з сервера, в результаті чого запити будуть здійснюватися без автентифікації, що призведе до неавтентифікованих запитів, якщо сайт покладається на перевірку сесії користувача (для відправки cookie файлів в аргументі init options має бути задане значення властивості credentials відмінне від значення за замовчуванням omit).

Базовий запит на отримання даних дійсно простий у налаштуванні. Погляньте на наступний код:

fetch('http://example.com/movies.json') .then((response) => < return response.json(); >) .then((data) => < console.log(data); >);

Тут ми забираємо JSON файл по мережі та виводимо його вміст у консоль. Найпростіший спосіб використання fetch() полягає у викликі цієї функції з одним аргументом - рядком, що містить шлях до ресурсу, який ви хочете отримати - який повертає promise, що містить відповідь (об'єкт Response).

Звичайно, це просто HTTP-відповідь, а не фактичний JSON. Щоб витягти вміст тіла JSON з відповіді, ми використовуємо json() метод (визначений міксином Body, який реалізований в об'єктах Request та Response.)

Fetch-запити контролюються за допомогою директиви connect-src (Content Security Policy), а не директивою ресурсів.

Встановлення параметрів запиту

Метод fetch() може приймати другий параметр - об'єкт init, який дозволяє вам контролювати різні налаштування:

// Приклад відправки запиту POST: async function postData(url = "", data = <>) < // Default options є маркованим з * const response = await fetch (url, < method: "POST", // *GET, POST, PUT, DELETE, etc. mode: "cors", // no-cors; -if-cached credentials: "same-origin", // include, *same-origin, omit headers: < "Content-Type": "application/json", // 'Content-Type': 'application/x- www-form-urlencoded', >, redirect: "follow", // manual, *follow, error referrerPolicy: "no-referrer", // no-referrer, *client body: JSON.stringify(data), // body data type must match "Content-Type" header >); return await response.json(); // parses JSON response in native JavaScript objects > postData("https://example.com/answer", < answer: 42 >).then((data) => < console.log(data); // JSON data parsed by `response.json()` call>);

З докладним описом функції та повним списком параметрів ви можете ознайомитись на сторінці fetch() .

Надсилання запиту з обліковими даними

Щоб браузери могли надсилати запит з обліковими даними (навіть для cross-origin запитів), додайте credentials: 'include' в об'єкт init , що передається в метод fetch() :

fetch("https://example.com", < credentials: "include", >);

Якщо ви хочете надсилати запит з обліковими даними тільки якщо URL належить одному джерелу (origin) що і скрипт, що викликає, додайте credentials: 'same-origin'.

// Скрипт, що викликає, належить джерелу 'https://example.com' fetch("https://example.com", < credentials: "same-origin", >);

Навпаки, щоб бути впевненим, що облікові дані не передаються із запитом, використовуйте credentials: 'omit':

fetch("https://example.com", < credentials: "omit", >);

Надсилання даних у форматі JSON

За допомогою fetch() можна надсилати запити POST у форматі JSON.

const url = "https://example.com/profile"; const data = <username: "example">; try < const response = await fetch(url, < method: "POST", // або 'PUT' body: JSON.stringify(data), // дані можуть бути 'рядком' або ! headers: < "Content-Type" : "application/json", >, >); const json = await response.json(); console.log("Успіх:", JSON.stringify(json)); > catch (error)

Завантаження файлу на сервер

На сервер можна завантажити файл, використовуючи комбінацію HTML-елемента, FormData() та fetch().

const formData = новий FormData(); const fileField = document.querySelector('input[type="file"]'); formData.append("username", "abc123"); formData.append("avatar", fileField.files[0]); try < const response = await fetch("https://example.com/profile/avatar", < метод: "PUT", body: formData, >); const result = await response.json(); console.log("Успіх:", JSON.stringify(result)); > catch (error)

Завантаження кількох файлів на сервер

На сервер можна завантажити кілька файлів, використовуючи комбінацію HTML-елемента, FormData() та fetch().

const formData = новий FormData(); const photos = document.querySelector('input[type="file"][multiple]'); formData.append("title", "Моя відпустка у Вегасі"); for (let i = 0; i < photos.files.length; i++) < formData.append("photos", photos.files[i]); >try < const response = await fetch("https://example.com/posts", < method: "POST", body: formData, >); const result = await response.json(); console.log("Успіх:", JSON.stringify(result)); > catch (error)

Обробка текстового файлу рядково

Фрагменти даних, отримані з відповіді, не розбиваються на рядки автоматично (принаймні з достатньою точністю) і є не рядками, а об'єктами Uint8Array . Якщо ви хочете завантажити текстовий файл і обробляти його в міру завантаження рядковим чином, то на вас самих лягає тягар відповідальності за обробку всіх згаданих моментів. Як приклад, далі представлений один із способів подібної обробки за допомогою створення рядкового ітератора (для простоти прийняті такі припущення: текст надходить у кодуванні UTF-8 і помилки отримання не обробляються).

async function* makeTextFileLineIterator(fileURL) < const utf8Decoder = новий TextDecoder("utf-8"); let response = await fetch(fileURL); let reader = response.body.getReader(); let < value: chunk, done: readerDone >= await reader.read(); chunk = chunk? utf8Decoder.decode(chunk): ""; let re = /n|r|n/gm; let startIndex = 0; let result; for(;;) < let result = re.exec(chunk); if (!result) < if (readerDone) < break; >let remainder = chunk.substr(startIndex); ( < value: chunk, done: readerDone >= await reader.read()); chunk = remainder + (chunk ? utf8Decoder.decode(chunk) : ""); startIndex = re.lastIndex = 0; continue; > yield chunk.substring(startIndex, result.index); startIndex = re.lastIndex; > if (startIndex < chunk.length) < //останній рядок не має символу перекладу рядка в кінці yield chunk.substr(startIndex); >> для звільнення (let line of makeTextFileLineIterator(urlOfFile))

Перевірка успішності запиту

У методі fetch() promise буде відхилено (reject) з TypeError , коли трапиться помилка мережі або не буде налаштовано CORS на стороні запитуваного сервера, хоча зазвичай це означає проблеми доступу або аналогічні - для прикладу, 404 не є мережевою помилкою. Для достовірної перевірки успішності fetch() включатиме перевірку того, що promise успішний (resolved), потім перевірку того, що значення властивості Response.ok є true. Код виглядатиме приблизно так:

try < const response = await fetch("flowers.jpg"); if (!response.ok) < throw new Error("Відповідь мережі був не ok."); > const myBlob = await response.blob(); const objectURL = URL.createObjectURL(myBlob); myImage.src = objectURL; > catch (error)

Складання об'єкта запиту

Замість передачі шляху ресурсу, який ви хочете запросити викликом fetch(), ви можете створити об'єкт запиту, використовуючи конструктор Request() і передати його в fetch() аргументом:

const myHeaders = New Headers(); const myInit = < method: "GET", headers: myHeaders, mode: "cors", cache: "default", >; const myRequest = new Request("flowers.jpg", myInit); const response = await fetch(myRequest); const myBlob = await response.blob(); const objectURL = URL.createObjectURL(myBlob); myImage.src = objectURL;

Конструктор Request() приймає такі самі параметри, як і метод fetch(). Ви можете навіть передати існуючий об'єкт запиту для створення його копії:

const anotherRequest = новий Request(myRequest, myInit);

Досить зручно, коли тіла запиту та відповіді використовуються один раз (прим.пер.: "are one use only"). Створення копії, як показано, дозволяє вам використовувати запит/відповідь повторно, при зміні опцій init, при бажанні. Копія повинна бути зроблена до прочитання тіла, а читання тіла у копії також помітить його прочитаним у вихідному запиті.

Також є метод clone() , який створює копії. Обидва методи створення копії припинять роботу з помилкою, якщо тіло оригінального запиту або відповіді вже було прочитано, але читання тіла клонованої відповіді або запиту не призведе до маркування оригінальної запиту.

Заголовки

Інтерфейс Headers дозволяє створити ваш власний об'єкт заголовків через конструктор Headers() . Об'єкт заголовків - проста мультикарта імен-значень:

const content = "Hello World"; const myHeaders = New Headers(); myHeaders.append("Content-Type", "text/plain"); myHeaders.append("Content-Length", content.length.toString()); myHeaders.append("X-Custom-Header", "ProcessThisImmediately");

Це може бути досягнуто шляхом передачі масиву масивів чи літерального об'єкта конструктору:

const myHeaders = New Headers(< "Content-Type": "text/plain", "Content-Length": content.length.toString(), "X-Custom-Header": "ProcessThisImmediately", >);

Вміст може бути запитаний та витягнутий:

console.log(myHeaders.has("Content-Type")); // true console.log(myHeaders.has("Set-Cookie")); // false myHeaders.set("Content-Type", "text/html"); myHeaders.append("X-Custom-Header", "AnotherValue"); console.log(myHeaders.get("Content-Length")); // 11 console.log(myHeaders.get("X-Custom-Header")); // ["ProcessThisImmediately", "AnotherValue"] myHeaders.delete("X-Custom-Header"); console.log(myHeaders.get("X-Custom-Header")); // []

Деякі з цих операцій можуть бути використані тільки в ServiceWorkers , але вони надають зручніший API для маніпуляції заголовками.

Всі методи Headers викидають TypeError, якщо ім'я заголовка не є валідним ім'ям HTTP Header. Операції мутації викинуть TypeError якщо є захист від мутації (дивіться нижче) (прим.пер.: "If there is an immutable guard"). Інакше вони перериваються мовчки. Наприклад:

const myResponse = Response.error(); try < myResponse.headers.set("Origin", "http://mybank.com"); >catch (e)

Хорошим варіантом використання заголовків є перевірка коректності типу контенту перед його обробкою. Наприклад:

try <const response = await fetch(myRequest); const contentType = response.headers.get("content-type"); if (!contentType || !contentType.includes("application/json")) < throw new TypeError("Ой, ми не отримали JSON!"); > const json = await response.json(); /_ Подальша обробка JSON _/; > catch (error)

Захист

З того часу, як заголовки можуть передаватися в запиті, прийматися у відповіді і мають різні обмеження щодо того, яка інформація може і повинна бути змінена, заголовки мають властивість guard. Це не поширюється на Web, але впливає те, які операції мутації доступні об'єкта заголовків.

none: за замовчуванням.request: захист об'єкта заголовків, отриманого за запитом ( Request.headers ).request-no-cors: захист об'єкта заголовків, отриманого за запитом створеного з Request.mode no-cors.response: Response.headers ).immutable: в основному використовується в ServiceWorkers; робить об'єкт заголовків read-only.

Примітка: ви не можете додати або встановити request захищені Headers' заголовок Content-Length. Аналогічно, вставка Set-Cookie у заголовок відповіді неприпустима: ServiceWorkers не допускають встановлення cookies через синтезовані відповіді.

Як ви бачили вище, екземпляр Response буде повернутий, коли fetch() проміс буде виконаний.

Властивості об'єкта-відповіді, які найчастіше використовуються:

Response.status — Цілочисленне (за промовчанням 200) містить код статусу відповіді. Response.statusText — Рядок (за умовчанням "OK"), який відповідає HTTP коду статусу. Response.ok - як сказано раніше, це коротка властивість для спрощення перевірки на те, що статус відповіді знаходиться десь між 200-299 включно. Це властивість типу Boolean.

Вони також можуть бути створені за допомогою JavaScript, але реальна користь від цього є лише при використанні сервіс-воркерів, коли ви надаєте власну відповідь на запит за допомогою методу respondWith() :

const myBody = новий Blob(); addEventListener("fetch", function (event) < // ServiceWorker перехоплює fetch event.respondWith( new Response(myBody, < headers: < "Content-Type": "text/plain" >, >), ); >);

Конструктор Response() приймає два необов'язкові аргументи - тіло для відповіді та об'єкт init (аналогічний тому, що приймає Request() )

Примітка: Метод error() просто повертає відповідь на помилку. Аналогічно, redirect() повертає відповідь, що призводить до перенаправлення на вказану URL-адресу. Вони також належать лише до Service Workers.

Тіло

Запит та відповідь можуть містити дані тіла. Тіло є екземпляром будь-якого з наступних типів:

Body домішка визначає такі методи для вилучення тіла (реалізовані як Request так Response ). Всі вони повертають promise, який зрештою виконується і виводить вміст.

Це робить використання нетекстових даних легшим, ніж XMR.

У запиті можна встановити параметри для надсилання тіла запиту:

const form = New FormData(document.getElementById("login-form")); fetch("/login", < method: "POST", body: form, >);

Параметри request та response (and by extension the fetch() function) по можливості повертають коректні типи даних. Параметр request також автоматично встановить Content-Type у заголовок, якщо його не було встановлено зі словника.

Функція виявлення

Підтримка Fetch API може бути виявлена ​​шляхом перевірки наявності Headers, Request, Response або fetch () в області видимості Window або Worker. Для прикладу:

Дивіться також

fetch ( )

За допомогою функції fetch ( ) можна надсилати мережні запити на сервер як отримувати, так і надсилати дані. Метод повертає проміс з об'єктом відповіді, де знаходиться додаткова інформація (статус відповіді, заголовки) та відповідь на запит.

Як зрозуміти

Скопіювати посилання "Як зрозуміти" Скопійовано

Браузер надає глобальний API для роботи із запитами та відповідями HTTP. Раніше для подібної роботи використовувався XMLHttpRequest, проте fetch ( ) більш гнучка та потужна альтернатива. Він зрозуміліший і простіший у використанні через те, що використовує Promise.

Як пишеться

Скопіювати посилання "Як пишеться" Скопійовано

Функція fetch ( ) приймає два параметри:

  • url - адреса, за якою потрібно зробити запит;
  • options (необов'язковий) — об'єкт конфігурації, в якому можна налаштувати метод та тіло запиту, заголовки та багато іншого.

За промовчанням виклик fetch ( ) робить GET-запит за вказаною адресою. Базовий виклик для отримання даних можна записати таким чином:

fetch('http://jsonplaceholder.typicode.com/posts')
fetch('http://jsonplaceholder.typicode.com/posts')





Результатом виклику fetch() буде Promise, в якому міститься спеціальний об'єкт відповіді Response. Цей об'єкт має два важливі для нас поля:

  • ok - приймає стан true або false та повідомляє про успішність запиту;
  • json - метод, виклик якого повертає результат запиту у вигляді JSON.

У наступному прикладі використовуємо .then ( ) - Обробник результату, отриманого від асинхронної операції. Оброблювач дочекається відповіді від сервера, приймає відповідь, і в даному випадку неявно поверне відповідь, оброблену методом .json ( ).

У прикладі функція then поверне інший проміс (їх можна поєднувати). Коли відрізається проміс (r.json()), який повернула функція then, буде викликаний наступний колбек у ланцюжку.

fetch('http://jsonplaceholder.typicode.com/posts')
.then((response) => response.json()
// Отримаємо відповідь у вигляді масиву з об'єктів:
// [, , , . ])
fetch('http://jsonplaceholder.typicode.com/posts')
.then((response)
=> response.json()
// Отримаємо відповідь у вигляді масиву з об'єктів:
// [, , , . ]
)





За допомогою другого аргументу options можна передати налаштування запиту. Наприклад, можна змінити метод та додати тіло запиту, якщо ми хочемо не отримувати, а надсилати дані. Також у запит можна додати заголовки у вигляді об'єкта або спеціального класу Headers.

const newPost =  title: 'foo',
body: 'bar',
userId: 1,>
fetch('https://jsonplaceholder.typicode.com/posts',  method: 'POST', // Тут також можуть бути GET, PUT, DELETE
// Тіло запиту в JSON-форматі
body: JSON.stringify(newPost),
headers:  // Додаємо необхідні заголовки
'Content-type': 'application/json; charset = UTF-8 ',
>,>)
.then((response) => response.json())
.then((data) =>  console.log(data)
// >)
const newPost =

title:
'foo',
body:
'bar',
userId:
1,
>
fetch('https://jsonplaceholder.typicode.com/posts',

метод:
'POST',
// Тут також можуть бути GET, PUT, DELETE
// Тіло запиту в JSON-форматі
body:
JSON.stringify(newPost),
headers:

// Додаємо необхідні заголовки
'Content-type':
'application/json; charset = UTF-8 ',
>,
>)
.then((response)
=> response.json())
.then((data)
=>

console.log(data)
// >
)




Cookies

Скопіювати посилання "Cookies" Скопійовано

За промовчанням fetch ( ) запити не включають cookies, і тому авторизовані запити на сервері можуть не пройти. Для цього необхідно додати в налаштування поле credentials:

fetch('https://somesite.com/admin',  метод: 'GET',
// Або same-origin, якщо можна робити такі запити
// Тільки в межах цього домену
credentials: 'include',>)
fetch('https://somesite.com/admin',

метод:
'GET',
// Або same-origin, якщо можна робити такі запити
// Тільки в межах цього домену
credentials:
'include',
>)




Обробка помилок

Скопіювати посилання "Обробка помилок" Скопійовано

Будь-яка відповідь на запит через fetch ( ) , наприклад, HTTP-код 400, 404 або 500, переводить Promise у стан fulfilled. Проміс перейде у стан rejected тільки якщо запит не трапився через збій мережі або щось завадило виконання fetch ( ) .

// Запит поверне помилку "404 Not Found"fetch('https://jsonplaceholder.typicode.com/there-is-no-such-route').catch(
() =>
console.log('Error occurred!')
>)// Ніколи не виконається
// Запит поверне помилку "404 Not Found"
fetch('https://jsonplaceholder.typicode.com/there-is-no-such-route').catch(
()
=>

console.log('Error occurred!')
>
)
// Ніколи не виконається




Щоб обробити помилку запиту, потрібно звертати увагу на поле ok в об'єкті відповіді Response . У разі помилки запиту воно дорівнюватиме false .

fetch('https://jsonplaceholder.typicode.com/there-is-no-such-route')
.then((response) =>  // Перевіряємо успішність запиту та викидаємо помилку
if (!response.ok)  throw new Error('Error occurred!')
>
return response.json()
>)
// Тепер потрапимо сюди, оскільки викинули помилку
.catch((err) =>  console.log(err)
>)// Error: Error occurred!
fetch('https://jsonplaceholder.typicode.com/there-is-no-such-route')
.then((response)
=>

// Перевіряємо успішність запиту та викидаємо помилку
if
(!response.ok)

throw
new
Error('Error occurred!')
>
return response.json()
>)
// Тепер потрапимо сюди, оскільки викинули помилку
.catch((err)
=>

console.log(err)
>
)
// Error: Error occurred!




На практиці

Скопіювати посилання "На практиці" Скопійовано

Єгор Огарков радить

Скопіювати посилання "Егор Огарков радить" Скопійовано

Скасування запиту

Скопіювати посилання "Скасувати запит" Скопіювати

Іноді може виникнути потреба перервати запит, наприклад, коли авторизація користувача прострочена або користувач самостійно хоче скасувати запит (скасував скачування файлу).

const controller = новий AbortController()
function fetchData()  return fetch('http://jsonplaceholder.typicode.com/posts',  сигнал: controller.signal,
>)
.then((response) => response.json())
.catch((e) =>  console.log(e)
>
)>
fetchData()
// Якщо запит ще не виконався, він буде перерваний.// Перерваний fetch поверне Promise з помилкоюcontroller.abort()
const controller =
new
AbortController()
function
fetchData()

return
fetch('http://jsonplaceholder.typicode.com/posts',

signal: controller.signal,
>)
.then((response)
=> response.json())
.catch((e)
=>

console.log(e)
>
)
>
fetchData()
// Якщо запит ще не виконався, він буде перерваний.
// Перерваний fetch поверне Promise з помилкою controller.abort()




Запит не виконається, в консолі буде помилка The user aborted a request . Якщо заглянути в інструменти розробника, там можна побачити скасований статус у запиту.

Завантаження файлу на сервер

Скопіювати посилання "Завантаження файлу на сервер" Скопійовано

За допомогою fetch ( ) можна завантажувати файли на сервер, наприклад коли користувач хоче завантажити свій аватар у профіль. Надсилання файлів можна здійснювати за допомогою спеціального об'єкта Form Data. Покажемо на прикладі обробника відправлення форми:






form
id="form">
input
type="file"
id="avatar">
button
type="submit">Завантажитиbutton>
form>




// Знаходимо елемент із файломconst fileInput = document.getElementById('avatar')const form = document.getElementById('form')
function handleSubmit(event)  event.preventDefault()
const formData = новий FormData()
// Додаємо файли з інпуту до даних
for (let i = 0; i < fileInput.files.length; i++)  const file = fileInput.files[i]
formData.append('avatar', file, file.name)
>
// Надсилаємо файли на сервер
fetch('https://backend.com/api/upload',  method: "POST",
body: formData,
>)>
form.addEventListener('submit', handleSubmit)
// Знаходимо елемент із файлом
const fileInput =
document.getElementById('avatar')
const form =
document.getElementById('form')
function
handleSubmit(event)
 event.preventDefault()
const formData =
new
FormData()
// Додаємо файли з інпуту до даних
for
(let i =
0; i  fileInput.files.length; i++)

const file = fileInput.files[i] formData.append('avatar', file, file.name)
>
// Надсилаємо файли на сервер
fetch('https://backend.com/api/upload',

метод:
"POST",
body: formData,
>)
> form.addEventListener('submit', handleSubmit)




Завантаження даних з результатом прогресу

Скопіювати посилання "Скачування даних з результатом прогресу" Скопійовано

Щоб отримувати поточний прогрес завантаження файлу або будь-яких інших даних, використовуйте властивість body об'єкта Response , який повертається до Promise після виклику fetch ( ) . Поле body є «потоком для читання» (Readable Stream). Це спеціальний об'єкт, який дає можливість отримувати інформацію частинами, у міру її надходження на клієнт.

Спробуємо в такий спосіб завантажити миле відео, як білий котик дружить із величезним псом.

fetch('https://i.imgur.com/C5QXZ7u.mp4').then(async (response) =>  let received = 0
// Отримуємо потік у змінну
const reader = response.body.getReader()
// Зчитуємо загальну довжину даних
const contentLength = parseInt(response.headers.get('Content-Length'), 10)
while (true)  // Після виклику read() повертається об'єкт, у якому
// done - boolean-значення про те, чи закінчилася інформація,
// value - масив байт, які прийшли цього разу
const < done, value >= await reader.read()
якщо (зроблено)  console.log('Отримано 100%')
break
>
received += Math.ceil(contentLength / value.length)
console.log(`Отримано $%`)
>>)
fetch('https://i.imgur.com/C5QXZ7u.mp4').then(async
(response)
=>

let received =
0
// Отримуємо потік у змінну
const reader = response.body.getReader()
// Зчитуємо загальну довжину даних
const contentLength =
parseInt(response.headers.get('Content-Length'),
10)
while
(true)

// Після виклику read() повертається об'єкт, у якому
// done - boolean-значення про те, чи закінчилася інформація,
// value - масив байт, які прийшли цього разу
const
 done, value >
=
await reader.read()
if
(done)

console.log('Отримано 100%')
break
> received +=
Math.ceil(contentLength / value.length)
console.log(`Отримано
$received>%`)
>
>)




Схожі статті

  • Що йде після терабайту пам'яті
  • Чому кішки стають спокійнішими після стерилізації
  • Коли можна робити губи трубочкою після збільшення
  • Чи можна на пошті сплатити після отримання
  • Чим удобрювати лохину після цвітіння
  • Як боротися з Лімфостазом після мастектомії
  • Як швидко відновитись після фізичних навантажень
  • Скільки зберігається зброя після смерті власника
  • Недавні статті

  • Чому взуття скрипить при ходьбі
  • Коли день народження у стрічці
  • Чи можна кішці їсти сіль
  • Варіанти планування ділянки 15 соток прямокутної форми
  • Що означає півмісяця знак
  • Рейсмусовий верстат для чого
  • У якому віці парують свиней
  • У чому полягає принцип нарахування та у яких випадках він застосовується