# XMLHttpRequest: индикация прогресса
Запрос `XMLHttpRequest` состоит из двух фаз:
Стадия закачки (upload). На ней данные загружаются на сервер. Эта фаза может быть долгой для POST-запросов.
Стадия скачивания (download). После того, как данные загружены, браузер скачивает ответ с сервера. Если он большой, то это может занять существенное время.
Для каждой стадии предусмотрены события, "рассказывающие" о процессе выполнения.
[cut]
## XMLHttpRequestUpload
Для отслеживания прогресса на стадии закачки существует объект [XMLHttpRequestUpload](https://xhr.spec.whatwg.org/#xmlhttprequesteventtarget), доступный как `xhr.upload`.
У него нет методов, он предназначен исключительно для обработки событий при закачке.
Вот их полный список:
`loadstart`
`progress`
`abort`
`error`
`load`
`timeout`
`loadend`
Пример установки обработчиков на стадию закачки:
```js
xhr.upload.onprogress = function(event) {
alert('Загружено на сервер ' + event.loaded + ' байт из '+ event.total);
}
xhr.upload.onload = function() {
alert('Данные полностью загружены на сервер!');
}
xhr.upload.onerror = function() {
alert('Произошла ошибка при загрузке данных на сервер!');
}
```
После того, как загрузка завершена, будет начато скачивание ответа.
На этой фазе `xhr.upload` уже не нужен, а в дело вступают обработчики событий на самом объекте `xhr`, в частности `xhr.onprogress`:
```js
xhr.onprogress = function(event) {
alert('Получено с сервера ' + event.loaded + ' байт из '+ event.total);
}
```
Все события, возникающие в этих обработчиках, имеют тип [ProgressEvent](https://xhr.spec.whatwg.org/#progressevent), то есть имеют свойства `loaded` -- количество уже пересланных данных в байтах и `total` -- общее количество данных.
## Загрузка файла с индикатором прогресса
Современный `XMLHttpRequest` позволяет отправить на сервер всё, что угодно. Текст, файл, форму.
Мы, для примера, рассмотрим загрузку файла с индикацией прогресса. Это требует от браузера поддержки [File API](http://www.w3.org/TR/FileAPI/), то есть исключает IE9-.
File API позволяет получить доступ к содержимому файла, который перенесён в браузер при помощи Drag'n'Drop или выбран в поле формы.
Форма для выбора файла с обработчиком `submit`:
```html
```
Здесь мы почти не пользуемся File API. Его роль -- получить файл из формы через свойство `files` элемента `` и далее мы сразу передадим его `xhr.send` в функции `upload`:
```js
function upload(file) {
var xhr = new XMLHttpRequest();
// обработчики можно объединить в один,
// если status == 200, то это успех, иначе ошибка
xhr.onload = xhr.onerror = function() {
if(this.status == 200) {
log("success");
} else {
log("error " + this.status);
}
};
// обработчик для закачки
xhr.upload.onprogress = function(event) {
log(event.loaded + ' / '+ event.total);
}
xhr.open("POST", "upload", true);
xhr.send(file);
}
```
Полный пример, основанный на коде выше:
[codetabs src="progress"]
## Событие onprogress в деталях
Событие `onprogress` имеет одинаковый вид при закачке на сервер (`xhr.upload.onprogress`) и при получении ответа (`xhr.onprogress`).
Оно получает объект `event` типа [ProgressEvent](https://xhr.spec.whatwg.org/#progressevent) со свойствами:
`loaded`
Сколько байт уже переслано.
Имеется в виду только тело запроса, заголовки не учитываются.
`lengthComputable`
Если `true`, то известно полное количество байт для пересылки, и оно хранится в свойстве `total`.
`total`
Общее количество байт для пересылки, если известно.
При HTTP-запросах оно передаётся в заголовке `Content-Length`.
При закачке на сервер браузер всегда знает полный размер пересылаемых данных. Поэтому в `xhr.upload.onprogress` значение `lengthComputable` всегда будет `true`, а `total` -- содержать этот размер.
При скачивании данных -- уже задача сервера поставить этот заголовок. Если его нет, то `total = 0`, а для того, чтобы понять, что данных не `0`, а неизвестное количество -- служит `lengthComputable`, которое в данном случае равно `false`.
Ещё особенности, которые необходимо учитывать при использовании `onprogress`:
**Событие происходит при каждом полученном/отправленном байте, но не чаще чем раз в 50мс.**
Это обозначено в [спецификации progress notifications](http://www.w3.org/TR/XMLHttpRequest/#make-progress-notifications).
**При получении данных доступен `xhr.responseText`.**
Можно до окончания запроса заглянуть в него и прочитать текущие полученные данные. Важно, что при пересылке строки в кодировке UTF-8 русские символы кодируются 2 байтами. Возможно, что в конце одного пакета данных окажется первая половинка символа, а в начале следующего -- вторая. Поэтому полагаться на то, что до окончания запроса в `responoseText` находится корректная строка нельзя. Исключение -- заведомо однобайтные символы, например цифры.
**При закачки `xhr.upload.onprogress` не гарантирует обработку загрузки сервером.**
Событие `xhr.upload.onprogress` срабатывает, когда данные отправлены браузером. Но оно не гарантирует, что сервер получил, обработал и записал данные на диск. Он говорит лишь о самом факте отправки.
Поэтому прогресс-индикатор, получаемый при его помощи, носит приблизительный и оптимистичный характер.
## Файлы и формы
Выше мы использовали `xhr.send(file)` для посылки файл пересылается в теле запроса.
При этом посылается только содержимое файла.
Если нужно дополнительно передать имя файла или что-то ещё -- это можно удобно сделать через форму, при помощи объекта [FormData](https://developer.mozilla.org/en-US/docs/DOM/XMLHttpRequest/FormData/Using_FormData_Objects):
Создадим форму `formData` и прибавим к ней поле с файлом `file` и именем `"myfile"`:
```js
var formData = new FormData();
formData.append("myfile", file);
xhr.send(formData);
```
Данные будут отправлены в кодировке `multipart/form-data`. Серверный фреймворк обработает это как обычную форму.