191 lines
11 KiB
Markdown
191 lines
11 KiB
Markdown
# XMLHttpRequest: индикация прогресса
|
||
|
||
Запрос `XMLHttpRequest` состоит из двух фаз:
|
||
<ol>
|
||
<li>Стадия закачки (upload). На ней данные загружаются на сервер. Эта фаза может быть долгой для POST-запросов. Для отслеживания прогресса на стадии закачки существует объект типа [XMLHttpRequestUpload](https://xhr.spec.whatwg.org/#xmlhttprequesteventtarget), доступный как `xhr.upload` и события на нём.</li>
|
||
<li>Стадия скачивания (download). После того, как данные загружены, браузер скачивает ответ с сервера. Если он большой, то это может занять существенное время. На этой стадии используется обработчик `xhr.onprogress`.</li>
|
||
</ol>
|
||
|
||
Далее -- обо всём по порядку.
|
||
|
||
[cut]
|
||
|
||
## Стадия закачки
|
||
|
||
На стадии закачки для получения информации используем объект `xhr.upload`. У этого объекта нет методов, он только генерирует события в процессе закачки. А они-то как раз и нужны.
|
||
|
||
Вот полный список событий:
|
||
<ul>
|
||
<li>`loadstart`</li>
|
||
<li>`progress`</li>
|
||
<li>`abort`</li>
|
||
<li>`error`</li>
|
||
<li>`load`</li>
|
||
<li>`timeout`</li>
|
||
<li>`loadend`</li>
|
||
</ul>
|
||
|
||
Пример установки обработчиков на стадию закачки:
|
||
|
||
```js
|
||
xhr.upload.onprogress = function(event) {
|
||
alert( 'Загружено на сервер ' + event.loaded + ' байт из ' + event.total );
|
||
}
|
||
|
||
xhr.upload.onload = function() {
|
||
alert( 'Данные полностью загружены на сервер!' );
|
||
}
|
||
|
||
xhr.upload.onerror = function() {
|
||
alert( 'Произошла ошибка при загрузке данных на сервер!' );
|
||
}
|
||
```
|
||
|
||
## Стадия скачивания
|
||
|
||
После того, как загрузка завершена, и сервер соизволит ответить на запрос, `XMLHttpRequest` начнёт скачивание ответа сервера.
|
||
|
||
На этой фазе `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 или выбран в поле формы, и отправить его при помощи `XMLHttpRequest`.
|
||
|
||
Форма для выбора файла с обработчиком `submit`:
|
||
|
||
```html
|
||
<form name="upload">
|
||
<input type="file" name="myfile">
|
||
<input type="submit" value="Загрузить">
|
||
</form>
|
||
|
||
<script>
|
||
document.forms.upload.onsubmit = function() {
|
||
var input = this.elements.myfile;
|
||
var file = input.files[0];
|
||
if (file) {
|
||
*!*
|
||
upload(file);
|
||
*/!*
|
||
}
|
||
return false;
|
||
}
|
||
</script>
|
||
```
|
||
|
||
Мы получаем файл из формы через свойство `files` элемента `<input>` и передаём его в функцию `upload`:
|
||
|
||
```js
|
||
function upload(file) {
|
||
|
||
var xhr = new XMLHttpRequest();
|
||
|
||
// обработчик для закачки
|
||
xhr.upload.onprogress = function(event) {
|
||
log(event.loaded + ' / ' + event.total);
|
||
}
|
||
|
||
// обработчики успеха и ошибки
|
||
// если status == 200, то это успех, иначе ошибка
|
||
xhr.onload = xhr.onerror = function() {
|
||
if (this.status == 200) {
|
||
log("success");
|
||
} else {
|
||
log("error " + this.status);
|
||
}
|
||
};
|
||
|
||
|
||
xhr.open("POST", "upload", true);
|
||
xhr.send(file);
|
||
|
||
}
|
||
```
|
||
|
||
Этот код отправит файл на сервер и будет сообщать о прогрессе при его закачке (`xhr.upload.onprogress`), а также об окончании запроса (`xhr.onload`, `xhr.onerror`).
|
||
|
||
Полный пример индикации прогресса при загрузке, основанный на коде выше:
|
||
|
||
[codetabs src="progress"]
|
||
|
||
## Событие onprogress в деталях
|
||
|
||
При обработке события `onprogress` есть ряд важных тонкостей.
|
||
|
||
Можно, конечно, их игнорировать, но лучше бы знать.
|
||
|
||
Заметим, что событие, возникающее при `onprogress`, имеет одинаковый вид на стадии закачки (в обработчике `xhr.upload.onprogress`) и при получении ответа (в обработчике `xhr.onprogress`).
|
||
|
||
Оно представляет собой объект типа [ProgressEvent](https://xhr.spec.whatwg.org/#progressevent) со свойствами:
|
||
|
||
<dl>
|
||
<dt>`loaded`</dt>
|
||
<dd>Сколько байт уже переслано.
|
||
|
||
Имеется в виду только тело запроса, заголовки не учитываются.</dd>
|
||
<dt>`lengthComputable`</dt>
|
||
<dd>Если `true`, то известно полное количество байт для пересылки, и оно хранится в свойстве `total`.</dd>
|
||
<dt>`total`</dt>
|
||
<dd>Общее количество байт для пересылки, если известно.
|
||
|
||
А может ли оно быть неизвестно?
|
||
|
||
<ul>
|
||
<li>При закачке на сервер браузер всегда знает полный размер пересылаемых данных, так что `total` всегда содержит конкретное количество байт, а значение `lengthComputable` всегда будет `true`.</li>
|
||
<li>При скачивании данных -- обычно сервер в начале сообщает их общее количество в HTTP-заголовке `Content-Length`. Но он может и не делать этого, например если сам не знает, сколько данных будет или если генерирует их динамически. Тогда `total` будет равно `0`. А чтобы отличить нулевой размер данных от неизвестного -- как раз служит `lengthComputable`, которое в данном случае равно `false`.</li>
|
||
</ul>
|
||
</dd>
|
||
</dl>
|
||
|
||
Ещё особенности, которые необходимо учитывать при использовании `onprogress`:
|
||
|
||
<ul>
|
||
<li>**Событие происходит при каждом полученном/отправленном байте, но не чаще чем раз в 50мс.**
|
||
|
||
Это обозначено в [спецификации progress notifications](http://www.w3.org/TR/XMLHttpRequest/#make-progress-notifications).
|
||
</li>
|
||
<li>**В процессе получения данных, ещё до их полной передачи, доступен `xhr.responseText`, но он не обязательно содержит корректную строку.**
|
||
|
||
Можно до окончания запроса заглянуть в него и прочитать текущие полученные данные. Важно, что при пересылке строки в кодировке UTF-8 кириллические символы, как, впрочем, и многие другие, кодируются 2 байтами. Возможно, что в конце одного пакета данных окажется первая половинка символа, а в начале следующего -- вторая. Поэтому полагаться на то, что до окончания запроса в `responoseText` находится корректная строка нельзя. Она может быть обрезана посередине символа.
|
||
|
||
Исключение -- заведомо однобайтные символы, например цифры или латинница.</li>
|
||
<li>**Сработавшее событие `xhr.upload.onprogress` не гарантирует, что данные дошли.**
|
||
|
||
Событие `xhr.upload.onprogress` срабатывает, когда данные отправлены браузером. Но оно не гарантирует, что сервер получил, обработал и записал данные на диск. Он говорит лишь о самом факте отправки.
|
||
|
||
Поэтому прогресс-индикатор, получаемый при его помощи, носит приблизительный и оптимистичный характер.</li>
|
||
</ul>
|
||
|
||
|
||
## Файлы и формы
|
||
|
||
Выше мы использовали `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`. Серверный фреймворк увидит это как обычную форму с файлом, практически все серверные технологии имеют их встроенную поддержку. Индикация прогресса реализуется точно так же.
|
||
|