существуетКак реализовать контроль параллелизма в JavaScript?В этой статье брат Абао подробно анализируетasync-poolКак использовать эту библиотекуPromise.all
иPromise.race
Функция реализует параллелизм управления асинхронными задачами. В этой статье Baoge расскажет, как использоватьasync-poolпредоставляется этой библиотекойasyncPool
функция для параллельной загрузки больших файлов.
Я считаю, что некоторые мелкие партнеры уже поняли решение для загрузки больших файлов.При загрузке больших файлов, чтобы повысить эффективность загрузки, мы обычно используемBlob.sliceМетод обрезает большой файл в соответствии с указанным размером, а затем запускает многопоточную загрузку по частям. После успешной загрузки всех частей он уведомляет сервер о необходимости их объединения.
Итак, можем ли мы принять аналогичную идею для загрузки больших файлов? Поддержка на сервереRange
При условии заголовка запроса мы также можем реализовать функцию многопоточной загрузки блока, как показано на следующем рисунке:
Прочитав приведенную выше картинку, я полагаю, что вы уже имеете определенное представление о схеме загрузки больших файлов. Далее, давайте сначала представим запросы диапазона HTTP.
Следуйте «Дорога полного стека», чтобы прочитать 4 бесплатные электронные книги (всего более 30 000 загрузок) и 11 руководств по Vue 3 для продвинутых пользователей.
1. Запрос области HTTP
Запросы области протокола HTTP позволяют серверу отправлять клиенту только часть сообщения HTTP. Запросы диапазона полезны при передаче больших медиафайлов или в сочетании с функцией возобновления загрузки файла. если присутствует в ответеAccept-Ranges
заголовок (и его значение не "none"), то сервер поддерживает запросы диапазона.
В заголовке Range можно одновременно запросить несколько частей, и сервер вернет их в виде составного файла. Если сервер возвращает ответ диапазона, вам нужно использовать206 Partial Contentкод состояния. Если запрошенный диапазон недействителен, сервер вернет416 Range Not SatisfiableКод состояния, указывающий на ошибку клиента. Серверу разрешено игнорировать заголовок Range и, таким образом, возвращать весь файл с кодом состояния 200 .
1.1 Синтаксис диапазона
Range: <unit>=<range-start>-
Range: <unit>=<range-start>-<range-end>
Range: <unit>=<range-start>-<range-end>, <range-start>-<range-end>
Range: <unit>=<range-start>-<range-end>, <range-start>-<range-end>, <range-start>-<range-end>
-
unit
: единица измерения, используемая для запроса диапазона, обычно байты. -
<range-start>
: целое число, представляющее начальное значение диапазона в указанных единицах измерения. -
<range-end>
: целое число, представляющее конечное значение диапазона в указанных единицах измерения.Это значение является необязательным и, если его нет, указывает, что область действия распространяется до конца документа.
пониматьRange
После синтаксиса давайте взглянем на реальный пример использования:
1.1.1 Единая область применения
$ curl http://i.imgur.com/z4d4kWk.jpg -i -H "Range: bytes=0-1023"
1.1.2 Несколько областей применения
$ curl http://www.example.com -i -H "Range: bytes=0-50, 100-150"
Что ж, сначала здесь будут представлены соответствующие знания о HTTP-запросе диапазона, давайте перейдем к теме и начнем знакомить, как реализовать загрузку больших файлов.
2. Как реализовать загрузку больших файлов
Для того, чтобы дать вам лучшее понимание того, что следует, давайте взглянем на общую блок-схему:
Разобравшись с процессом загрузки больших файлов, давайте сначала определим некоторые вспомогательные функции, задействованные в описанном выше процессе.
2.1 Определение вспомогательных функций
2.1.1 Определение функции getContentLength
Как следует из названияgetContentLength
Функция для получения длины файла. В этой функции мы отправляемHEAD
запрос, затем прочитайте из заголовков ответаContent-Length
информацию для получения текущейurl
Соответствует длине содержимого файла.
function getContentLength(url) {
return new Promise((resolve, reject) => {
let xhr = new XMLHttpRequest();
xhr.open("HEAD", url);
xhr.send();
xhr.onload = function () {
resolve(
~~xhr.getResponseHeader("Content-Length")
);
};
xhr.onerror = reject;
});
}
2.1.2 Определение функции asyncPool
существуетКак реализовать контроль параллелизма в JavaScript?В этой статье мы знакомимasyncPool
Функция, которая используется для реализации управления параллелизмом для асинхронных задач. Функция принимает 3 параметра:
-
poolLimit
(числовой тип): указывает количество одновременных ограничений; -
array
(Тип массива): представляет массив задач; -
iteratorFn
(Тип функции): представляет итеративную функцию, которая используется для обработки каждого элемента задачи и возвращает объект Promise или асинхронную функцию.
async function asyncPool(poolLimit, array, iteratorFn) {
const ret = []; // 存储所有的异步任务
const executing = []; // 存储正在执行的异步任务
for (const item of array) {
// 调用iteratorFn函数创建异步任务
const p = Promise.resolve().then(() => iteratorFn(item, array));
ret.push(p); // 保存新的异步任务
// 当poolLimit值小于或等于总任务个数时,进行并发控制
if (poolLimit <= array.length) {
// 当任务完成后,从正在执行的任务数组中移除已完成的任务
const e = p.then(() => executing.splice(executing.indexOf(e), 1));
executing.push(e); // 保存正在执行的异步任务
if (executing.length >= poolLimit) {
await Promise.race(executing); // 等待较快的任务执行完成
}
}
}
return Promise.all(ret);
}
2.1.3 Определение функции getBinaryContent
getBinaryContent
Функция используется для инициирования запроса диапазона на основе входящих параметров, тем самым загружая блоки данных файла в пределах указанного диапазона:
function getBinaryContent(url, start, end, i) {
return new Promise((resolve, reject) => {
try {
let xhr = new XMLHttpRequest();
xhr.open("GET", url, true);
xhr.setRequestHeader("range", `bytes=${start}-${end}`); // 请求头上设置范围请求信息
xhr.responseType = "arraybuffer"; // 设置返回的类型为arraybuffer
xhr.onload = function () {
resolve({
index: i, // 文件块的索引
buffer: xhr.response, // 范围请求对应的数据
});
};
xhr.send();
} catch (err) {
reject(new Error(err));
}
});
}
должен быть в курсеArrayBuffer
Объекты используются для представления общих буферов фиксированной длины необработанных двоичных данных.мы не можем работать напрямуюArrayBuffer
, но работать с объектами типизированного массива или объектами DataView, которые представляют данные в буфере в определенном формате, и использовать эти форматы для чтения и записи содержимого буфера..
2.1.4 Определение функции конкатенации
Поскольку он не может управляться напрямуюArrayBuffer
объект, поэтому нам нужно сначала поставитьArrayBuffer
объект, преобразованный вUint8Array
объект, а затем выполните операцию слияния. определено нижеconcatenate
Функция состоит в том, чтобы объединить загруженные блоки данных файла.Конкретный код выглядит следующим образом:
function concatenate(arrays) {
if (!arrays.length) return null;
let totalLength = arrays.reduce((acc, value) => acc + value.length, 0);
let result = new Uint8Array(totalLength);
let length = 0;
for (let array of arrays) {
result.set(array, length);
length += array.length;
}
return result;
}
2.1.5 Определение функции saveAs
saveAs
Функция используется для реализации функции сохранения файла клиента, вот только простая реализация. В реальных проектах вы можете рассмотреть возможность использованияFileSaver.js . если ты правFileSaver.js Если вам интересно, как это работает, вы можете прочитатьДавайте поговорим о 15.5K FileSaver, как он работает?Эта статья.
function saveAs({ name, buffers, mime = "application/octet-stream" }) {
const blob = new Blob([buffers], { type: mime });
const blobUrl = URL.createObjectURL(blob);
const a = document.createElement("a");
a.download = name || Math.random();
a.href = blobUrl;
a.click();
URL.revokeObjectURL(blob);
}
существуетsaveAs
В функции мы использовали Blob и Object URL. Где URL-адрес объекта — это псевдопротокол, который позволяет использовать объекты Blob и File в качестве источников URL-адресов для изображений, ссылок для загрузки двоичных данных и т. д. В браузере мы используемURL.createObjectURL
метод для создания URL-адреса объекта, который принимаетBlob
объекта и создать для него уникальный URL видаblob:<origin>/<uuid>
, соответствующий пример выглядит следующим образом:
blob:https://example.org/40a5fb5a-d56d-4a33-b4e2-0acf6a8e5f641
Внутри браузера для каждого проходаURL.createObjectURL
Сгенерированный URL-адрес сохраняет URL-адрес → Карта BLOB-объектов. Поэтому такие URL короче, но к ним можно получить доступBlob
. Сгенерированный URL-адрес действителен только в том случае, если текущий документ открыт. Что ж, сначала здесь будет представлено связанное содержимое URL-адреса объекта.Если вы знаете больше о Blob и URL-адресе объекта, вы можете прочитатьКапли, о которых вы не зналиЭта статья.
2.1.6 Определение функции загрузки
download
Функция используется для реализации операции загрузки, она поддерживает 3 параметра:
-
url
(Строковый тип): адрес предварительно загруженного ресурса; -
chunkSize
(числовой тип): размер блока в байтах; -
poolLimit
(числовой тип): указывает предел параллелизма.
async function download({ url, chunkSize, poolLimit = 1 }) {
const contentLength = await getContentLength(url);
const chunks = typeof chunkSize === "number" ? Math.ceil(contentLength / chunkSize) : 1;
const results = await asyncPool(
poolLimit,
[...new Array(chunks).keys()],
(i) => {
let start = i * chunkSize;
let end = i + 1 == chunks ? contentLength - 1 : (i + 1) * chunkSize - 1;
return getBinaryContent(url, start, end, i);
}
);
const sortedBuffers = results
.map((item) => new Uint8Array(item.buffer));
return concatenate(sortedBuffers);
}
2.2 Пример использования загрузки больших файлов
Основываясь на вспомогательных функциях, определенных выше, мы можем легко загружать большие файлы параллельно.Конкретный код выглядит следующим образом:
function multiThreadedDownload() {
const url = document.querySelector("#fileUrl").value;
if (!url || !/https?/.test(url)) return;
console.log("多线程下载开始: " + +new Date());
download({
url,
chunkSize: 0.1 * 1024 * 1024,
poolLimit: 6,
}).then((buffers) => {
console.log("多线程下载结束: " + +new Date());
saveAs({ buffers, name: "我的压缩包", mime: "application/zip" });
});
}
Так как полный пример кода содержит много контента, Brother Abao не будет помещать конкретный код. Заинтересованные партнеры могут посетить следующий адрес, чтобы просмотреть пример кода.
Полный пример кода:gist.GitHub.com/Semelinker/8…
Здесь мы смотрим на текущие результаты примера загрузки большого файла:
3. Резюме
В этой статье описывается, как использовать в JavaScriptasync-poolпредоставляется этой библиотекойasyncPool
функция для параллельной загрузки больших файлов. Помимо введенияasyncPool
В дополнение к функции A Baoge также рассказал, как получить размер файла с помощью запроса HEAD, как инициировать запрос диапазона HTTP и как сохранить файл на стороне клиента. На самом деле использоватьasyncPool
Функция может реализовать не только параллельную загрузку больших файлов, но и реализовать параллельную загрузку больших файлов.Заинтересованные партнеры могут попробовать сами.
Следуйте «Дорога полного стека», чтобы прочитать 4 бесплатные электронные книги (всего более 30 000 загрузок) и 11 руководств по Vue 3 для продвинутых пользователей.Друзья, которые хотят вместе изучать TS/Vue 3.0, могут добавить Abaoge WeChat —— semlinker.