{"id":350649,"date":"2023-07-22T21:00:20","date_gmt":"2023-07-22T21:00:20","guid":{"rendered":"http:\/\/savepearlharbor.com\/?p=350649"},"modified":"-0001-11-30T00:00:00","modified_gmt":"-0001-11-29T21:00:00","slug":"","status":"publish","type":"post","link":"https:\/\/savepearlharbor.com\/?p=350649","title":{"rendered":"<span>\u041c\u0438\u043d\u0438\u043c\u0430\u043b\u0438\u0441\u0442\u0438\u0447\u043d\u044b\u0439 \u0437\u0430\u0433\u0440\u0443\u0437\u0447\u0438\u043a \u0444\u0430\u0439\u043b\u043e\u0432<\/span>"},"content":{"rendered":"<div><\/div>\n<div id=\"post-content-body\">\n<div>\n<div class=\"article-formatted-body article-formatted-body article-formatted-body_version-2\">\n<div xmlns=\"http:\/\/www.w3.org\/1999\/xhtml\">\n<p><em>\u041f\u0440\u0438\u0432\u0435\u0442, \u0447\u0443\u0432\u0430\u043a. \u042d\u0442\u043e \u044f. \u0422\u043e \u0435\u0441\u0442\u044c \u0442\u044b, \u0442\u043e\u043b\u044c\u043a\u043e \u0438\u0437 \u0431\u0443\u0434\u0443\u0449\u0435\u0433\u043e. \u0423\u0432\u044b, \u0442\u0443\u0442 \u0443 \u043d\u0430\u0441 \u0432 2023 \u043d\u0438\u043a\u0430\u043a\u0438\u0445 \u043b\u0435\u0442\u0430\u044e\u0449\u0438\u0445 \u043c\u0430\u0448\u0438\u043d \u0438 \u0441\u043a\u0435\u0439\u0442\u043e\u0432 \u043d\u0435\u0442. \u0418 \u0447\u0442\u043e \u0441\u0430\u043c\u043e\u0435 \u0441\u043c\u0435\u0448\u043d\u043e\u0435 &#8212; \u043f\u0435\u0440\u0435\u0434\u0430\u0447\u0430 \u0444\u0430\u0439\u043b\u043e\u0432 \u043c\u0435\u0436\u0434\u0443 \u0434\u0435\u0432\u0430\u0439\u0441\u0430\u043c\u0438 \u0434\u043e \u0441\u0438\u0445 \u043f\u043e\u0440 \u043f\u0440\u043e\u0431\u043b\u0435\u043c\u0430. \u041d\u0430\u0434\u0435\u044e\u0441\u044c, \u0442\u044b \u044d\u0442\u043e \u043f\u0440\u043e\u0447\u0438\u0442\u0430\u0435\u0448\u044c \u0438 \u0441\u043e\u0437\u0434\u0430\u0448\u044c \u0434\u043b\u044f \u0441\u0435\u0431\u044f \u0432\u0440\u0435\u043c\u0435\u043d\u043d\u0443\u044e \u043e\u0441\u044c \u043f\u043e\u043b\u0443\u0447\u0448\u0435.<\/em><\/p>\n<p>\u041d\u0443 \u0430 \u043f\u043e\u043a\u0430 \u044f \u0437\u0430\u0441\u0442\u0440\u044f\u043b \u0437\u0434\u0435\u0441\u044c \u0438 \u0432\u044b\u043d\u0443\u0436\u0434\u0435\u043d \u043a\u0430\u043a-\u0442\u043e \u0441\u043a\u0438\u043d\u0443\u0442\u044c \u0444\u043e\u0442\u043a\u0438 \u0441\u043e \u0441\u0432\u043e\u0435\u0433\u043e \u0442\u0435\u043b\u0435\u0444\u043e\u043d\u0430, \u0443 \u043a\u043e\u0442\u043e\u0440\u043e\u0433\u043e \u043f\u043e\u0447\u0435\u043c\u0443-\u0442\u043e \u043e\u0442\u0432\u0430\u043b\u0438\u043b\u0441\u044f MTP. \u0412 \u0440\u0430\u0431\u043e\u0442\u0435 \u0443 \u043c\u0435\u043d\u044f &#8212; \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0430 \u0444\u0438\u0434\u0431\u044d\u043a\u0430 \u0434\u043b\u044f \u043f\u043e\u043b\u043d\u043e\u0441\u0442\u044c\u044e \u0441\u0442\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u043e\u0433\u043e \u0441\u0430\u0439\u0442\u0430 \u0438 \u044f \u043f\u043e\u0434\u0443\u043c\u0430\u043b &#8212; \u041e! \u0410 \u0432\u0435\u0434\u044c \u0442\u0430\u043c \u0437\u0430\u0433\u0440\u0443\u0437\u0447\u0438\u043a \u0444\u0430\u0439\u043b\u043e\u0432 \u0431\u0443\u0434\u0435\u0442 \u043e\u0447\u0435\u043d\u044c \u043a\u0441\u0442\u0430\u0442\u0438, \u0437\u0430\u043e\u0434\u043d\u043e \u0438 \u0444\u043e\u0442\u043a\u0438 \u0441\u043a\u0438\u043d\u0443. \u0418 \u043a\u0430\u043a \u0442\u043e\u043b\u044c\u043a\u043e \u044f \u043d\u0430\u0447\u0430\u043b \u0435\u0433\u043e \u0434\u0435\u043b\u0430\u0442\u044c, \u0432 \u043e\u0434\u043d\u043e\u043c \u0438\u0437 \u0441\u043f\u0438\u0441\u043a\u043e\u0432 \u0440\u0430\u0441\u0441\u044b\u043b\u043a\u0438 Devuan \u0432\u0438\u0436\u0443 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0435: \u043a\u0430\u043a \u0434\u043e\u0441\u0442\u0430\u043b\u0438 \u044d\u0442\u0438 \u0433\u0440\u0451\u0431\u0430\u043d\u044b\u0435 \u0442\u0435\u043b\u0435\u0444\u043e\u043d\u044b, \u043a\u0430\u043a \u0441\u043a\u0438\u043d\u0443\u0442\u044c \u0444\u0430\u0439\u043b\u044b \u043f\u043e\u043c\u043e\u0433\u0438\u0442\u0435 \u043f\u043e\u0436\u0430\u043b\u0443\u0439\u0441\u0442\u0430.<\/p>\n<p>\u041d\u0443, \u0434\u0443\u043c\u0430\u044e, \u0440\u0430\u0437 \u043d\u0435 \u044f \u0442\u0430\u043a\u043e\u0439 \u043e\u0434\u0438\u043d, \u0437\u043d\u0430\u0447\u0438\u0442 \u043e\u043d\u043e \u0442\u043e\u0433\u043e \u0441\u0442\u043e\u0438\u0442. \u041a\u043e\u0433\u0434\u0430-\u0442\u043e \u044f \u0434\u0435\u043b\u0430\u043b \u043f\u043e\u0434\u043e\u0431\u043d\u043e\u0435, \u0434\u0430\u0432\u043d\u044b\u043c-\u0434\u0430\u0432\u043d\u043e, \u0435\u0449\u0451 \u0432\u043e \u0432\u0440\u0435\u043c\u0435\u043d\u0430 jQuery, \u043d\u043e \u0442\u043e\u0433\u0434\u0430 \u0431\u044b\u043b \u043a\u0430\u043a\u043e\u0439-\u0442\u043e \u0433\u043e\u0442\u043e\u0432\u044b\u0439 \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442. \u0410 \u0441\u0435\u0439\u0447\u0430\u0441 \u044f \u043d\u0438\u0447\u0435\u0433\u043e \u0441\u0442\u043e\u0440\u043e\u043d\u043d\u0435\u0433\u043e \u043d\u0435 \u0445\u043e\u0442\u0435\u043b. \u041f\u043e\u043b\u0435\u0437 \u0432 MDN. \u041e\u0441\u043c\u044b\u0441\u043b\u0435\u043d\u043d\u043e \u0432\u0441\u0451 \u0441\u043a\u043e\u043f\u0438\u043f\u0430\u0441\u0442\u0438\u043b, \u0438 \u0432\u043e\u0442 \u0434\u043e\u043a\u043b\u0430\u0434\u044b\u0432\u0430\u044e \u043e \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442\u0430\u0445. \u041a\u043e\u0435-\u0447\u0442\u043e, \u043a\u043e\u043d\u0435\u0447\u043d\u043e, \u043f\u043e\u0432\u044b\u0447\u0438\u0441\u0442\u0438\u043b, \u043d\u0430\u043f\u0440\u0438\u043c\u0435\u0440, \u043e\u0442\u0441\u043b\u0435\u0436\u0438\u0432\u0430\u043d\u0438\u0435 \u0444\u0430\u0439\u043b\u043e\u0432 \u0441 \u043e\u0434\u0438\u043d\u0430\u043a\u043e\u0432\u044b\u043c\u0438 \u0438\u043c\u0451\u043d\u0430\u043c\u0438, \u0435\u0441\u043b\u0438 \u043e\u043d\u0438 \u0438\u0437 \u0440\u0430\u0437\u043d\u044b\u0445 \u043a\u0430\u0442\u0430\u043b\u043e\u0433\u043e\u0432, \u043f\u043e\u043a\u0430\u0437 \u043c\u0438\u043d\u0438\u0430\u0442\u044e\u0440, \u043d\u043e \u044d\u0442\u043e \u0438\u0441\u043a\u043b\u044e\u0447\u0438\u0442\u0435\u043b\u044c\u043d\u043e \u0440\u0430\u0434\u0438 \u043f\u0440\u043e\u0441\u0442\u043e\u0442\u044b \u0438\u0437\u043b\u043e\u0436\u0435\u043d\u0438\u044f. \u0422\u0430\u043a\u0438\u0435 \u043c\u0435\u043b\u043e\u0447\u0438 \u0432\u044b \u0438 \u0441\u0430\u043c\u0438 \u043b\u0435\u0433\u043a\u043e \u0441\u0434\u0435\u043b\u0430\u0435\u0442\u0435.<\/p>\n<p>\u0418\u0442\u0430\u043a, \u0431\u0430\u0437\u043e\u0432\u044b\u0439 \u043c\u043e\u0434\u0443\u043b\u044c \u0437\u0430\u0433\u0440\u0443\u0437\u0447\u0438\u043a\u0430 \u043d\u0430 Javascript. \u041c\u043e\u0434\u0443\u043b\u0435\u043c \u0435\u0433\u043e \u043d\u0430\u0437\u0432\u0430\u0442\u044c \u043c\u043e\u0436\u043d\u043e \u0441 \u0431\u043e\u043b\u044c\u0448\u043e\u0439 \u043d\u0430\u0442\u044f\u0436\u043a\u043e\u0439, \u0442\u0430\u043a \u043a\u0430\u043a \u0432\u0435\u0441\u044c Javascript \u0438 CSS \u0443 \u043c\u0435\u043d\u044f \u0441\u043e\u0431\u0438\u0440\u0430\u0435\u0442\u0441\u044f \u0432 \u043e\u0434\u0438\u043d HTML \u0444\u0430\u0439\u043b, \u0443\u043f\u0430\u043a\u043e\u0432\u044b\u0432\u0430\u0435\u0442\u0441\u044f \u0432 gz, \u0438 \u0434\u0430\u043b\u044c\u0448\u0435 nginx \u0440\u0430\u0437\u0434\u0430\u0451\u0442 \u0435\u0433\u043e \u043d\u0430\u043b\u0435\u0432\u043e \u0438 \u043d\u0430\u043f\u0440\u0430\u0432\u043e \u043c\u0430\u043a\u0441\u0438\u043c\u0430\u043b\u044c\u043d\u043e \u0431\u044b\u0441\u0442\u0440\u043e. \u0412\u043d\u0443\u0442\u0440\u0438 HTML \u043c\u043e\u0434\u0443\u043b\u044c Javascript \u0441\u0442\u0430\u043d\u043e\u0432\u0438\u0442\u0441\u044f \u0430\u043d\u043e\u043d\u0438\u043c\u043d\u044b\u043c \u0431\u0435\u0437 \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e\u0441\u0442\u0438 \u044d\u043a\u0441\u043f\u043e\u0440\u0442\u0430 \u0447\u0435\u0433\u043e-\u043b\u0438\u0431\u043e, \u043f\u043e\u044d\u0442\u043e\u043c\u0443 \u043f\u0440\u0438\u0445\u043e\u0434\u0438\u0442\u0441\u044f \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u0441\u0442\u0430\u0440\u044b\u0435 \u043d\u0435\u0434\u043e\u0431\u0440\u044b\u0435 \u043c\u0435\u0442\u043e\u0434\u044b.<\/p>\n<pre><code class=\"javascript\">(() => {  class FileUploader {     constructor(settings)     {         const default_settings = {             url: '\/',             chunk_size: 512 * 1024,  \/\/ \u043f\u043e\u0441\u043b\u0435\u0434\u043d\u0438\u0439 chunk \u043c\u043e\u0436\u0435\u0442 \u0431\u044b\u0442\u044c \u0432 \u043f\u043e\u043b\u0442\u043e\u0440\u0430 \u0440\u0430\u0437\u0430 \u0431\u043e\u043b\u044c\u0448\u0435,                                      \/\/ \u043d\u0435 \u0437\u0430\u0431\u044b\u0432\u0430\u0435\u043c \u043f\u0440\u043e \u043b\u0438\u043c\u0438\u0442\u044b request body \u043d\u0430 \u0441\u0435\u0440\u0432\u0435\u0440\u0435                                      \/\/ (\u0443 NGINX \u043f\u043e \u0443\u043c\u043e\u043b\u0447\u0430\u043d\u0438\u044e 1M)             file_name_header: 'File-Name'  \/\/ \u0447\u0442\u043e-\u043d\u0438\u0431\u0443\u0434\u044c \u0441\u0442\u0430\u043d\u0434\u0430\u0440\u0442\u043d\u043e\u0435 \u0442\u0438\u043f\u0430 Content-Disposition                                            \/\/ \u0431\u044b\u043b\u043e \u0431\u044b \u043b\u0443\u0447\u0448\u0435, \u043d\u043e \u0435\u0433\u043e \u0441\u043b\u043e\u0436\u043d\u0435\u0435 \u043f\u0430\u0440\u0441\u0438\u0442\u044c         };          this.settings = Object.assign({}, default_settings, settings);         this.upload_queue = [];     }      upload(file, params)     \/*      * \u0414\u043e\u0431\u0430\u0432\u043b\u044f\u0435\u043c \u0444\u0430\u0439\u043b \u0432 \u043e\u0447\u0435\u0440\u0435\u0434\u044c, \u0438 \u0435\u0441\u043b\u0438 \u0437\u0430\u0433\u0440\u0443\u0437\u043a\u0430 \u0435\u0449\u0451 \u043d\u0435 \u0432 \u043f\u0440\u043e\u0446\u0435\u0441\u0441\u0435 - \u0442\u043e\u0433\u0434\u0430 \u043d\u0430\u0447\u0438\u043d\u0430\u0435\u043c.      *\/     {         const start_upload = this.upload_queue.length == 0;          \/\/ \u0421\u043e\u0437\u0434\u0430\u0451\u043c file_item \u0438 \u0434\u043e\u0431\u0430\u0432\u043b\u044f\u0435\u043c \u0432 \u043d\u0430\u0447\u0430\u043b\u043e \u043e\u0447\u0435\u0440\u0435\u0434\u0438         const file_item = new FileItem(this, file, params);         this.upload_queue.push(file_item);          if(start_upload) {             \/\/ \u0435\u0441\u043b\u0438 \u0432\u044b\u0437\u044b\u0432\u0430\u0435\u043c \u0430\u0441\u0438\u043d\u0445\u0440\u043e\u043d\u043d\u0443\u044e \u0444\u0443\u043d\u043a\u0446\u0438\u044e \u0431\u0435\u0437 await, \u043f\u043e\u043b\u0443\u0447\u0438\u043c promise,             \/\/ \u043b\u0438\u0431\u043e fulfilled, \u043b\u0438\u0431\u043e pending. \u041d\u043e \u043e\u043d \u043d\u0430\u043c \u0432\u0441\u0451 \u0440\u0430\u0432\u043d\u043e \u043d\u0435 \u043d\u0443\u0436\u0435\u043d.             this._async_upload_files().then();         }     }      progress(file, params, chunk_start_percentage, chunk_end_percentage, percentage)     \/*      * \u042d\u0442\u043e\u0442 \u043c\u0435\u0442\u043e\u0434 \u0432\u044b\u0437\u044b\u0432\u0430\u0435\u0442\u0441\u044f \u0434\u043b\u044f \u043e\u0442\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u044f \u043f\u0440\u043e\u0433\u0440\u0435\u0441\u0441-\u0431\u0430\u0440\u0430.      * \u0420\u0435\u0430\u043b\u0438\u0437\u0443\u0435\u043c \u0435\u0433\u043e \u0432 \u043f\u0440\u043e\u0438\u0437\u0432\u043e\u0434\u043d\u043e\u043c \u043a\u043b\u0430\u0441\u0441\u0435.      *\/     {     }      async upload_complete(file, params)     \/*      * \u042d\u0442\u043e\u0442 \u043c\u0435\u0442\u043e\u0434 \u0432\u044b\u0437\u044b\u0432\u0430\u0435\u0442\u0441\u044f \u043f\u043e \u0437\u0430\u0432\u0435\u0440\u0448\u0435\u043d\u0438\u0438 \u0437\u0430\u0433\u0440\u0443\u0437\u043a\u0438.      * \u0420\u0435\u0430\u043b\u0438\u0437\u0443\u0435\u043c \u0435\u0433\u043e \u0432 \u043f\u0440\u043e\u0438\u0437\u0432\u043e\u0434\u043d\u043e\u043c \u043a\u043b\u0430\u0441\u0441\u0435.      *\/     {     }      async _async_upload_files()     {         \/\/ \u043e\u0431\u0440\u0430\u0431\u0430\u0442\u044b\u0432\u0430\u0435\u043c \u043e\u0447\u0435\u0440\u0435\u0434\u044c \u0437\u0430\u0433\u0440\u0443\u0437\u043a\u0438         while(this.upload_queue.length != 0) {             await this.upload_queue[0].upload();             this.upload_queue.shift();         }     } }  class FileItem \/*  * \u042d\u043b\u0435\u043c\u0435\u043d\u0442 \u043e\u0447\u0435\u0440\u0435\u0434\u0438 \u0437\u0430\u0433\u0440\u0443\u0437\u043a\u0438.  *\/ {     constructor(uploader, file, params)     {         this.uploader = uploader;         this.file = file;         this.params = params;     }      async upload()     {         var chunk_start = 0;         var chunk_size;         while(chunk_start &lt; this.file.size) {             const remaining_size = this.file.size - chunk_start;              \/\/ \u0437\u0430\u0433\u0440\u0443\u0436\u0430\u0435\u043c \u043a\u0443\u0441\u043a\u0430\u043c\u0438 default_chunk_size, \u043f\u043e\u0441\u043b\u0435\u0434\u043d\u0438\u0439 \u043a\u0443\u0441\u043e\u043a \u0434\u043e\u043f\u0443\u0441\u043a\u0430\u0435\u0442\u0441\u044f             \/\/ \u0432 \u043f\u043e\u043b\u0442\u043e\u0440\u0430 \u0440\u0430\u0437\u0430 \u0431\u043e\u043b\u044c\u0448\u0435, \u0447\u0435\u043c default_chunk_size             if(remaining_size &lt; 1.5 * this.uploader.settings.chunk_size) {                 chunk_size = remaining_size;             } else {                 chunk_size = this.uploader.settings.chunk_size;             }              const chunk = this.file.slice(chunk_start, chunk_start + chunk_size);             \/\/ XXX \u0441\u043e\u0445\u0440\u0430\u043d\u044f\u0442\u044c (start, end) \u0432 \u0441\u043b\u0430\u0439\u0441\u0435 - \u0433\u0440\u044f\u0437\u043d\u044b\u0439 \u0445\u0430\u043a, \u0430 \u0447\u0442\u043e \u0434\u0435\u043b\u0430\u0442\u044c?             chunk.start = chunk_start;             chunk.end = chunk_start + chunk_size;             while(true) {                 try {                     await this._upload_chunk(chunk);                     break;                 } catch(error) {                     console.log(`${this.file.name} upload error, retry in 5 seconds`);                     await new Promise(resolve => setTimeout(resolve, 5000));                 }             }              chunk_start += chunk_size;         }         await this.uploader.upload_complete(this.file, this.params);     }      _upload_chunk(chunk)     {         \/\/ \u042d\u0442\u0430 \u0444\u0443\u043d\u043a\u0446\u0438\u044f \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442 non-awaitable XMLHttpRequest, \u043f\u043e\u044d\u0442\u043e\u043c\u0443 \u043d\u0435 \u043c\u043e\u0436\u0435\u0442 \u0431\u044b\u0442\u044c async.         \/\/ \u041d\u043e \u043c\u044b \u0432\u044b\u0437\u044b\u0432\u0430\u0435\u043c \u0435\u0451 \u0441 await, \u0442\u0430\u043a \u0447\u0442\u043e \u0434\u043e\u043b\u0436\u043d\u044b \u0432\u0435\u0440\u043d\u0443\u0442\u044c promise.          const self = this;          return new Promise((resolve, reject) => {              const reader = new FileReader();             const xhr = new XMLHttpRequest();              xhr.upload.addEventListener(                 \"progress\",                 (e) => {                     if(e.lengthComputable) {                         const percentage = Math.round((e.loaded * 100) \/ e.total);                         self._update_progress(chunk, percentage);                     }                 },                 false             );              xhr.onreadystatechange = () => {                 if(xhr.readyState === xhr.DONE) {                     if(xhr.status === 200) {                         self._update_progress(chunk, 100);                         resolve(xhr.response);                     } else {                         reject({                             status: xhr.status,                             statusText: xhr.statusText                         });                     }                 }             };              xhr.onerror = () => {                 reject({                     status: xhr.status,                     statusText: xhr.statusText                 });             };              xhr.open('POST', this.uploader.settings.url);              const content_range = `bytes ${chunk.start}-${chunk.end - 1}\/${this.file.size}`;             xhr.setRequestHeader(\"Content-Range\", content_range);             xhr.setRequestHeader(\"Content-Type\", \"application\/octet-stream\");             xhr.setRequestHeader(this.uploader.settings.file_name_header, this.file.name);              reader.onload = (e) => {                 xhr.send(e.target.result);             };              reader.readAsArrayBuffer(chunk);             self._update_progress(chunk, 0);         });     }      _update_progress(chunk, percentage)     {         \/\/ \u0441\u0447\u0438\u0442\u0430\u0435\u043c \u043f\u0440\u043e\u0446\u0435\u043d\u0442\u044b \u0438 \u0432\u044b\u0437\u044b\u0432\u0430\u0435\u043c \u043c\u0435\u0442\u043e\u0434 progress         const chunk_start_percentage = chunk.start * 100 \/ this.file.size;         const chunk_end_percentage = chunk.end * 100 \/ this.file.size;         const upload_percentage = chunk_start_percentage + chunk.size * percentage \/ this.file.size;         this.uploader.progress(             this.file,             this.params,             chunk_start_percentage.toFixed(2),             chunk_end_percentage.toFixed(2),             upload_percentage.toFixed(2)         );     } }  \/\/ \u0442\u0438\u043f\u0430 \u044d\u043a\u0441\u043f\u043e\u0440\u0442\u0438\u0440\u0443\u0435\u043c FileUploader window.FileUploader = FileUploader;  })();<\/code><\/pre>\n<p>HTML \u0438 \u043e\u0441\u0442\u0430\u043b\u044c\u043d\u043e\u0439 Javascript:<\/p>\n<pre><code class=\"javascript\">&lt;h3>Upload Files&lt;\/h3> &lt;p>     &lt;button id=\"file-select\">Choose Files&lt;\/button> or drag and drop to the table below &lt;\/p> &lt;table id=\"file-list\">     &lt;thead>         &lt;tr>&lt;th>File name&lt;\/th>&lt;th>Size&lt;\/th>&lt;\/tr>     &lt;\/thead>     &lt;tbody>     &lt;\/tbody> &lt;\/table> &lt;template id=\"file-row\">     &lt;tr>&lt;td>&lt;\/td>&lt;td>&lt;\/td>&lt;\/tr> &lt;\/template> &lt;input type=\"file\" id=\"files-input\" multiple style=\"display:none\">  &lt;script>     const upload_complete_color = 'rgb(0,192,0,0.2)';     const chunk_complete_color = 'rgb(0,255,0,0.1)';      class Uploader extends FileUploader     {         constructor()         {             super({url: '\/api\/feedback\/upload'});              this.elem = {                 file_select:  document.getElementById(\"file-select\"),                 files_input:  document.getElementById(\"files-input\"),                 file_list:    document.getElementById(\"file-list\"),                 row_template: document.getElementById('file-row')             };             this.elem.tbody = this.elem.file_list.getElementsByTagName('tbody')[0];              this.row_index = 0;              this.set_event_handlers();         }          set_event_handlers()         {             const self = this;             this.elem.file_select.addEventListener(                 \"click\",                 () => { self.elem.files_input.click(); },                 false             );             this.elem.files_input.addEventListener(                 \"change\",                 () => { self.handle_files(self.elem.files_input.files) },                 false             );              function consume_event(e)             {                 e.stopPropagation();                 e.preventDefault();             }              function drop(e)             {                 consume_event(e);                 self.handle_files(e.dataTransfer.files);             }              this.elem.file_list.addEventListener(\"dragenter\", consume_event, false);             this.elem.file_list.addEventListener(\"dragover\", consume_event, false);             this.elem.file_list.addEventListener(\"drop\", drop, false);         }          progress(file, params, chunk_start_percentage, chunk_end_percentage, percentage)         {             params.progress_container.style.background = 'linear-gradient(to right, '                 + `${upload_complete_color} 0 ${percentage}%, `                 + `${chunk_complete_color} ${percentage}% ${chunk_end_percentage}%, `                 + `transparent ${chunk_end_percentage}%)`;         }          async upload_complete(file, params)         {             \/\/ \u043a\u0440\u0430\u0441\u0438\u043c \u0437\u0435\u043b\u0451\u043d\u044b\u043c \u0432\u0441\u044e \u0441\u0442\u0440\u043e\u043a\u0443             params.progress_container.style.background = upload_complete_color;             params.progress_container.nextSibling.style.background = upload_complete_color;         }          handle_files(files)         \/*          * \u043e\u0431\u0440\u0430\u0431\u0430\u0442\u044b\u0432\u0430\u0435\u043c \u0437\u0434\u0435\u0441\u044c \u0444\u0430\u0439\u043b\u044b \u043e\u0442 drag'n'drop \u0438\u043b\u0438 \u0434\u0438\u0430\u043b\u043e\u0433\u0430 \u0432\u044b\u0431\u043e\u0440\u0430          *\/         {             for(const file of files) {                 const cols = this.append_file(file.size);                 this.upload(file, {progress_container: cols[0]});             }         }          append_file(size)         \/*          * \u0414\u043e\u0431\u0430\u0432\u043b\u044f\u0435\u043c \u0444\u0430\u0439\u043b \u0432 \u0442\u0430\u0431\u043b\u0438\u0446\u0443, \u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0435\u043c \u0441\u043f\u0438\u0441\u043e\u043a \u044f\u0447\u0435\u0435\u043a.          *\/         {             const rows = this.elem.tbody.getElementsByTagName(\"tr\");             var row;             if(this.row_index >= rows.length) {                 row = this.append_row();             } else {                 row = rows[this.row_index];             }             this.row_index++;              const cols = row.getElementsByTagName(\"td\");             cols[1].textContent = size.toString();             return cols;         }          append_row()         \/*          * \u0414\u043e\u0431\u0430\u0432\u043b\u044f\u0435\u043c \u043f\u0443\u0441\u0442\u0443\u044e \u0441\u0442\u0440\u043e\u043a\u0443 \u043a \u0442\u0430\u0431\u043b\u0438\u0446\u0435.          *\/         {             const tbody = this.elem.file_list.getElementsByTagName('tbody')[0];             const row = this.elem.row_template.content.firstElementChild.cloneNode(true);             tbody.appendChild(row);             return row;         }       const uploader = new Uploader();      \/\/ \u0438\u043d\u0438\u0446\u0438\u0430\u043b\u0438\u0437\u0438\u0440\u0443\u0435\u043c \u0442\u0430\u0431\u043b\u0438\u0446\u0443 - \u0434\u043e\u0431\u0430\u0432\u043b\u044f\u0435\u043c \u043f\u044f\u0442\u044c \u043f\u0443\u0441\u0442\u044b\u0445 \u0441\u0442\u0440\u043e\u043a     for(let i = 0; i &lt; 5; i++) uploader.append_row();  &lt;\/script><\/code><\/pre>\n<p>\u0418, \u043d\u0430\u043a\u043e\u043d\u0435\u0446, \u043a\u0443\u0441\u043e\u0447\u0435\u043a \u0441\u0435\u0440\u0432\u0435\u0440\u043d\u043e\u0439 \u0447\u0430\u0441\u0442\u0438:<\/p>\n<pre><code class=\"python\">import os.path import re from starlette.responses import Response import aiofiles.os  # \u041d\u0438\u0447\u0435\u0433\u043e \u044d\u0442\u043e\u0433\u043e \u0432 aiofiles \u043d\u0435\u0442. \u041d\u0430 \u043c\u043e\u043c\u0435\u043d\u0442 \u043d\u0430\u043f\u0438\u0441\u0430\u043d\u0438\u044f, \u043f\u043e \u043a\u0440\u0430\u0439\u043d\u0435\u0439 \u043c\u0435\u0440\u0435. aiofiles.os.open = aiofiles.os.wrap(os.open) aiofiles.os.close = aiofiles.os.wrap(os.close) aiofiles.os.lseek = aiofiles.os.wrap(os.lseek) aiofiles.os.write = aiofiles.os.wrap(os.write)  re_content_range = re.compile(r'bytes\\s+(\\d+)-(\\d+)\/(\\d+)')  @expose(methods='POST') async def upload(self, request):     '''     \u041b\u043e\u0432\u0438\u043c \u0438 \u0437\u0430\u043f\u0438\u0441\u044b\u0432\u0430\u0435\u043c \u043a\u0443\u0441\u043e\u043a \u0444\u0430\u0439\u043b\u0430.     '''     data = await request.body()     filename = os.path.basename(request.headers['File-Name'])     start, end, size = [int(n) for n in re_content_range.search(request.headers['Content-Range']).groups()]      fd = await aiofiles.os.open(filename, os.O_CREAT | os.O_RDWR, mode=0o666)     try:         await aiofiles.os.lseek(fd, start, os.SEEK_SET)         await aiofiles.os.write(fd, data)     finally:         await aiofiles.os.close(fd)     return Response()<\/code><\/pre>\n<p>\u0427\u0442\u043e \u0441\u043a\u0430\u0437\u0430\u0442\u044c \u0432 \u0437\u0430\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0435? \u041c\u044b \u0431\u0435\u0433\u0430\u0435\u043c \u043f\u043e \u043a\u0440\u0443\u0433\u0443. \u041f\u0440\u043e\u0446\u0435\u0441\u0441\u043e\u0440\u044b \u043c\u043e\u0449\u043d\u0435\u0435, \u043f\u0430\u043c\u044f\u0442\u0438 \u0431\u043e\u043b\u044c\u0448\u0435, \u0430 \u0437\u0430\u0433\u0440\u0443\u0437\u0438\u0442\u044c \u0432\u0441\u0435 \u0444\u0430\u0439\u043b\u044b  \u0437\u0430 \u043e\u0434\u0438\u043d \u0440\u0430\u0437 \u043c\u043d\u0435 \u043d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c. \u0411\u0440\u0430\u0443\u0437\u0435\u0440 \u0432\u044b\u043b\u0435\u0442\u0430\u043b \u0441 OOM \u043f\u043e\u0441\u043b\u0435 20-30 \u0437\u0430\u0433\u0440\u0443\u0436\u0435\u043d\u043d\u044b\u0445 \u0444\u043e\u0442\u043e\u043a, \u0438 \u044d\u0442\u043e \u0431\u0435\u0437 \u043f\u043e\u043a\u0430\u0437\u0430 \u043c\u0438\u043d\u0438\u0430\u0442\u044e\u0440. \u0418\u043b\u0438 \u044f \u0433\u0434\u0435-\u0442\u043e \u043d\u0430\u043a\u043e\u0441\u044f\u0447\u0438\u043b?<\/p>\n<\/div>\n<\/div>\n<\/div>\n<p> <!----> <!----><\/div>\n<p> <!----> <!----><br \/> \u0441\u0441\u044b\u043b\u043a\u0430 \u043d\u0430 \u043e\u0440\u0438\u0433\u0438\u043d\u0430\u043b \u0441\u0442\u0430\u0442\u044c\u0438 <a href=\"https:\/\/habr.com\/ru\/articles\/749746\/\"> https:\/\/habr.com\/ru\/articles\/749746\/<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<div><\/div>\n<div id=\"post-content-body\">\n<div>\n<div class=\"article-formatted-body article-formatted-body article-formatted-body_version-2\">\n<div xmlns=\"http:\/\/www.w3.org\/1999\/xhtml\">\n<p><em>\u041f\u0440\u0438\u0432\u0435\u0442, \u0447\u0443\u0432\u0430\u043a. \u042d\u0442\u043e \u044f. \u0422\u043e \u0435\u0441\u0442\u044c \u0442\u044b, \u0442\u043e\u043b\u044c\u043a\u043e \u0438\u0437 \u0431\u0443\u0434\u0443\u0449\u0435\u0433\u043e. \u0423\u0432\u044b, \u0442\u0443\u0442 \u0443 \u043d\u0430\u0441 \u0432 2023 \u043d\u0438\u043a\u0430\u043a\u0438\u0445 \u043b\u0435\u0442\u0430\u044e\u0449\u0438\u0445 \u043c\u0430\u0448\u0438\u043d \u0438 \u0441\u043a\u0435\u0439\u0442\u043e\u0432 \u043d\u0435\u0442. \u0418 \u0447\u0442\u043e \u0441\u0430\u043c\u043e\u0435 \u0441\u043c\u0435\u0448\u043d\u043e\u0435 &#8212; \u043f\u0435\u0440\u0435\u0434\u0430\u0447\u0430 \u0444\u0430\u0439\u043b\u043e\u0432 \u043c\u0435\u0436\u0434\u0443 \u0434\u0435\u0432\u0430\u0439\u0441\u0430\u043c\u0438 \u0434\u043e \u0441\u0438\u0445 \u043f\u043e\u0440 \u043f\u0440\u043e\u0431\u043b\u0435\u043c\u0430. \u041d\u0430\u0434\u0435\u044e\u0441\u044c, \u0442\u044b \u044d\u0442\u043e \u043f\u0440\u043e\u0447\u0438\u0442\u0430\u0435\u0448\u044c \u0438 \u0441\u043e\u0437\u0434\u0430\u0448\u044c \u0434\u043b\u044f \u0441\u0435\u0431\u044f \u0432\u0440\u0435\u043c\u0435\u043d\u043d\u0443\u044e \u043e\u0441\u044c \u043f\u043e\u043b\u0443\u0447\u0448\u0435.<\/em><\/p>\n<p>\u041d\u0443 \u0430 \u043f\u043e\u043a\u0430 \u044f \u0437\u0430\u0441\u0442\u0440\u044f\u043b \u0437\u0434\u0435\u0441\u044c \u0438 \u0432\u044b\u043d\u0443\u0436\u0434\u0435\u043d \u043a\u0430\u043a-\u0442\u043e \u0441\u043a\u0438\u043d\u0443\u0442\u044c \u0444\u043e\u0442\u043a\u0438 \u0441\u043e \u0441\u0432\u043e\u0435\u0433\u043e \u0442\u0435\u043b\u0435\u0444\u043e\u043d\u0430, \u0443 \u043a\u043e\u0442\u043e\u0440\u043e\u0433\u043e \u043f\u043e\u0447\u0435\u043c\u0443-\u0442\u043e \u043e\u0442\u0432\u0430\u043b\u0438\u043b\u0441\u044f MTP. \u0412 \u0440\u0430\u0431\u043e\u0442\u0435 \u0443 \u043c\u0435\u043d\u044f &#8212; \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0430 \u0444\u0438\u0434\u0431\u044d\u043a\u0430 \u0434\u043b\u044f \u043f\u043e\u043b\u043d\u043e\u0441\u0442\u044c\u044e \u0441\u0442\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u043e\u0433\u043e \u0441\u0430\u0439\u0442\u0430 \u0438 \u044f \u043f\u043e\u0434\u0443\u043c\u0430\u043b &#8212; \u041e! \u0410 \u0432\u0435\u0434\u044c \u0442\u0430\u043c \u0437\u0430\u0433\u0440\u0443\u0437\u0447\u0438\u043a \u0444\u0430\u0439\u043b\u043e\u0432 \u0431\u0443\u0434\u0435\u0442 \u043e\u0447\u0435\u043d\u044c \u043a\u0441\u0442\u0430\u0442\u0438, \u0437\u0430\u043e\u0434\u043d\u043e \u0438 \u0444\u043e\u0442\u043a\u0438 \u0441\u043a\u0438\u043d\u0443. \u0418 \u043a\u0430\u043a \u0442\u043e\u043b\u044c\u043a\u043e \u044f \u043d\u0430\u0447\u0430\u043b \u0435\u0433\u043e \u0434\u0435\u043b\u0430\u0442\u044c, \u0432 \u043e\u0434\u043d\u043e\u043c \u0438\u0437 \u0441\u043f\u0438\u0441\u043a\u043e\u0432 \u0440\u0430\u0441\u0441\u044b\u043b\u043a\u0438 Devuan \u0432\u0438\u0436\u0443 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0435: \u043a\u0430\u043a \u0434\u043e\u0441\u0442\u0430\u043b\u0438 \u044d\u0442\u0438 \u0433\u0440\u0451\u0431\u0430\u043d\u044b\u0435 \u0442\u0435\u043b\u0435\u0444\u043e\u043d\u044b, \u043a\u0430\u043a \u0441\u043a\u0438\u043d\u0443\u0442\u044c \u0444\u0430\u0439\u043b\u044b \u043f\u043e\u043c\u043e\u0433\u0438\u0442\u0435 \u043f\u043e\u0436\u0430\u043b\u0443\u0439\u0441\u0442\u0430.<\/p>\n<p>\u041d\u0443, \u0434\u0443\u043c\u0430\u044e, \u0440\u0430\u0437 \u043d\u0435 \u044f \u0442\u0430\u043a\u043e\u0439 \u043e\u0434\u0438\u043d, \u0437\u043d\u0430\u0447\u0438\u0442 \u043e\u043d\u043e \u0442\u043e\u0433\u043e \u0441\u0442\u043e\u0438\u0442. \u041a\u043e\u0433\u0434\u0430-\u0442\u043e \u044f \u0434\u0435\u043b\u0430\u043b \u043f\u043e\u0434\u043e\u0431\u043d\u043e\u0435, \u0434\u0430\u0432\u043d\u044b\u043c-\u0434\u0430\u0432\u043d\u043e, \u0435\u0449\u0451 \u0432\u043e \u0432\u0440\u0435\u043c\u0435\u043d\u0430 jQuery, \u043d\u043e \u0442\u043e\u0433\u0434\u0430 \u0431\u044b\u043b \u043a\u0430\u043a\u043e\u0439-\u0442\u043e \u0433\u043e\u0442\u043e\u0432\u044b\u0439 \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442. \u0410 \u0441\u0435\u0439\u0447\u0430\u0441 \u044f \u043d\u0438\u0447\u0435\u0433\u043e \u0441\u0442\u043e\u0440\u043e\u043d\u043d\u0435\u0433\u043e \u043d\u0435 \u0445\u043e\u0442\u0435\u043b. \u041f\u043e\u043b\u0435\u0437 \u0432 MDN. \u041e\u0441\u043c\u044b\u0441\u043b\u0435\u043d\u043d\u043e \u0432\u0441\u0451 \u0441\u043a\u043e\u043f\u0438\u043f\u0430\u0441\u0442\u0438\u043b, \u0438 \u0432\u043e\u0442 \u0434\u043e\u043a\u043b\u0430\u0434\u044b\u0432\u0430\u044e \u043e \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442\u0430\u0445. \u041a\u043e\u0435-\u0447\u0442\u043e, \u043a\u043e\u043d\u0435\u0447\u043d\u043e, \u043f\u043e\u0432\u044b\u0447\u0438\u0441\u0442\u0438\u043b, \u043d\u0430\u043f\u0440\u0438\u043c\u0435\u0440, \u043e\u0442\u0441\u043b\u0435\u0436\u0438\u0432\u0430\u043d\u0438\u0435 \u0444\u0430\u0439\u043b\u043e\u0432 \u0441 \u043e\u0434\u0438\u043d\u0430\u043a\u043e\u0432\u044b\u043c\u0438 \u0438\u043c\u0451\u043d\u0430\u043c\u0438, \u0435\u0441\u043b\u0438 \u043e\u043d\u0438 \u0438\u0437 \u0440\u0430\u0437\u043d\u044b\u0445 \u043a\u0430\u0442\u0430\u043b\u043e\u0433\u043e\u0432, \u043f\u043e\u043a\u0430\u0437 \u043c\u0438\u043d\u0438\u0430\u0442\u044e\u0440, \u043d\u043e \u044d\u0442\u043e \u0438\u0441\u043a\u043b\u044e\u0447\u0438\u0442\u0435\u043b\u044c\u043d\u043e \u0440\u0430\u0434\u0438 \u043f\u0440\u043e\u0441\u0442\u043e\u0442\u044b \u0438\u0437\u043b\u043e\u0436\u0435\u043d\u0438\u044f. \u0422\u0430\u043a\u0438\u0435 \u043c\u0435\u043b\u043e\u0447\u0438 \u0432\u044b \u0438 \u0441\u0430\u043c\u0438 \u043b\u0435\u0433\u043a\u043e \u0441\u0434\u0435\u043b\u0430\u0435\u0442\u0435.<\/p>\n<p>\u0418\u0442\u0430\u043a, \u0431\u0430\u0437\u043e\u0432\u044b\u0439 \u043c\u043e\u0434\u0443\u043b\u044c \u0437\u0430\u0433\u0440\u0443\u0437\u0447\u0438\u043a\u0430 \u043d\u0430 Javascript. \u041c\u043e\u0434\u0443\u043b\u0435\u043c \u0435\u0433\u043e \u043d\u0430\u0437\u0432\u0430\u0442\u044c \u043c\u043e\u0436\u043d\u043e \u0441 \u0431\u043e\u043b\u044c\u0448\u043e\u0439 \u043d\u0430\u0442\u044f\u0436\u043a\u043e\u0439, \u0442\u0430\u043a \u043a\u0430\u043a \u0432\u0435\u0441\u044c Javascript \u0438 CSS \u0443 \u043c\u0435\u043d\u044f \u0441\u043e\u0431\u0438\u0440\u0430\u0435\u0442\u0441\u044f \u0432 \u043e\u0434\u0438\u043d HTML \u0444\u0430\u0439\u043b, \u0443\u043f\u0430\u043a\u043e\u0432\u044b\u0432\u0430\u0435\u0442\u0441\u044f \u0432 gz, \u0438 \u0434\u0430\u043b\u044c\u0448\u0435 nginx \u0440\u0430\u0437\u0434\u0430\u0451\u0442 \u0435\u0433\u043e \u043d\u0430\u043b\u0435\u0432\u043e \u0438 \u043d\u0430\u043f\u0440\u0430\u0432\u043e \u043c\u0430\u043a\u0441\u0438\u043c\u0430\u043b\u044c\u043d\u043e \u0431\u044b\u0441\u0442\u0440\u043e. \u0412\u043d\u0443\u0442\u0440\u0438 HTML \u043c\u043e\u0434\u0443\u043b\u044c Javascript \u0441\u0442\u0430\u043d\u043e\u0432\u0438\u0442\u0441\u044f \u0430\u043d\u043e\u043d\u0438\u043c\u043d\u044b\u043c \u0431\u0435\u0437 \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e\u0441\u0442\u0438 \u044d\u043a\u0441\u043f\u043e\u0440\u0442\u0430 \u0447\u0435\u0433\u043e-\u043b\u0438\u0431\u043e, \u043f\u043e\u044d\u0442\u043e\u043c\u0443 \u043f\u0440\u0438\u0445\u043e\u0434\u0438\u0442\u0441\u044f \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u0441\u0442\u0430\u0440\u044b\u0435 \u043d\u0435\u0434\u043e\u0431\u0440\u044b\u0435 \u043c\u0435\u0442\u043e\u0434\u044b.<\/p>\n<pre><code class=\"javascript\">(() => {  class FileUploader {     constructor(settings)     {         const default_settings = {             url: '\/',             chunk_size: 512 * 1024,  \/\/ \u043f\u043e\u0441\u043b\u0435\u0434\u043d\u0438\u0439 chunk \u043c\u043e\u0436\u0435\u0442 \u0431\u044b\u0442\u044c \u0432 \u043f\u043e\u043b\u0442\u043e\u0440\u0430 \u0440\u0430\u0437\u0430 \u0431\u043e\u043b\u044c\u0448\u0435,                                      \/\/ \u043d\u0435 \u0437\u0430\u0431\u044b\u0432\u0430\u0435\u043c \u043f\u0440\u043e \u043b\u0438\u043c\u0438\u0442\u044b request body \u043d\u0430 \u0441\u0435\u0440\u0432\u0435\u0440\u0435                                      \/\/ (\u0443 NGINX \u043f\u043e \u0443\u043c\u043e\u043b\u0447\u0430\u043d\u0438\u044e 1M)             file_name_header: 'File-Name'  \/\/ \u0447\u0442\u043e-\u043d\u0438\u0431\u0443\u0434\u044c \u0441\u0442\u0430\u043d\u0434\u0430\u0440\u0442\u043d\u043e\u0435 \u0442\u0438\u043f\u0430 Content-Disposition                                            \/\/ \u0431\u044b\u043b\u043e \u0431\u044b \u043b\u0443\u0447\u0448\u0435, \u043d\u043e \u0435\u0433\u043e \u0441\u043b\u043e\u0436\u043d\u0435\u0435 \u043f\u0430\u0440\u0441\u0438\u0442\u044c         };          this.settings = Object.assign({}, default_settings, settings);         this.upload_queue = [];     }      upload(file, params)     \/*      * \u0414\u043e\u0431\u0430\u0432\u043b\u044f\u0435\u043c \u0444\u0430\u0439\u043b \u0432 \u043e\u0447\u0435\u0440\u0435\u0434\u044c, \u0438 \u0435\u0441\u043b\u0438 \u0437\u0430\u0433\u0440\u0443\u0437\u043a\u0430 \u0435\u0449\u0451 \u043d\u0435 \u0432 \u043f\u0440\u043e\u0446\u0435\u0441\u0441\u0435 - \u0442\u043e\u0433\u0434\u0430 \u043d\u0430\u0447\u0438\u043d\u0430\u0435\u043c.      *\/     {         const start_upload = this.upload_queue.length == 0;          \/\/ \u0421\u043e\u0437\u0434\u0430\u0451\u043c file_item \u0438 \u0434\u043e\u0431\u0430\u0432\u043b\u044f\u0435\u043c \u0432 \u043d\u0430\u0447\u0430\u043b\u043e \u043e\u0447\u0435\u0440\u0435\u0434\u0438         const file_item = new FileItem(this, file, params);         this.upload_queue.push(file_item);          if(start_upload) {             \/\/ \u0435\u0441\u043b\u0438 \u0432\u044b\u0437\u044b\u0432\u0430\u0435\u043c \u0430\u0441\u0438\u043d\u0445\u0440\u043e\u043d\u043d\u0443\u044e \u0444\u0443\u043d\u043a\u0446\u0438\u044e \u0431\u0435\u0437 await, \u043f\u043e\u043b\u0443\u0447\u0438\u043c promise,             \/\/ \u043b\u0438\u0431\u043e fulfilled, \u043b\u0438\u0431\u043e pending. \u041d\u043e \u043e\u043d \u043d\u0430\u043c \u0432\u0441\u0451 \u0440\u0430\u0432\u043d\u043e \u043d\u0435 \u043d\u0443\u0436\u0435\u043d.             this._async_upload_files().then();         }     }      progress(file, params, chunk_start_percentage, chunk_end_percentage, percentage)     \/*      * \u042d\u0442\u043e\u0442 \u043c\u0435\u0442\u043e\u0434 \u0432\u044b\u0437\u044b\u0432\u0430\u0435\u0442\u0441\u044f \u0434\u043b\u044f \u043e\u0442\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u044f \u043f\u0440\u043e\u0433\u0440\u0435\u0441\u0441-\u0431\u0430\u0440\u0430.      * \u0420\u0435\u0430\u043b\u0438\u0437\u0443\u0435\u043c \u0435\u0433\u043e \u0432 \u043f\u0440\u043e\u0438\u0437\u0432\u043e\u0434\u043d\u043e\u043c \u043a\u043b\u0430\u0441\u0441\u0435.      *\/     {     }      async upload_complete(file, params)     \/*      * \u042d\u0442\u043e\u0442 \u043c\u0435\u0442\u043e\u0434 \u0432\u044b\u0437\u044b\u0432\u0430\u0435\u0442\u0441\u044f \u043f\u043e \u0437\u0430\u0432\u0435\u0440\u0448\u0435\u043d\u0438\u0438 \u0437\u0430\u0433\u0440\u0443\u0437\u043a\u0438.      * \u0420\u0435\u0430\u043b\u0438\u0437\u0443\u0435\u043c \u0435\u0433\u043e \u0432 \u043f\u0440\u043e\u0438\u0437\u0432\u043e\u0434\u043d\u043e\u043c \u043a\u043b\u0430\u0441\u0441\u0435.      *\/     {     }      async _async_upload_files()     {         \/\/ \u043e\u0431\u0440\u0430\u0431\u0430\u0442\u044b\u0432\u0430\u0435\u043c \u043e\u0447\u0435\u0440\u0435\u0434\u044c \u0437\u0430\u0433\u0440\u0443\u0437\u043a\u0438         while(this.upload_queue.length != 0) {             await this.upload_queue[0].upload();             this.upload_queue.shift();         }     } }  class FileItem \/*  * \u042d\u043b\u0435\u043c\u0435\u043d\u0442 \u043e\u0447\u0435\u0440\u0435\u0434\u0438 \u0437\u0430\u0433\u0440\u0443\u0437\u043a\u0438.  *\/ {     constructor(uploader, file, params)     {         this.uploader = uploader;         this.file = file;         this.params = params;     }      async upload()     {         var chunk_start = 0;         var chunk_size;         while(chunk_start &lt; this.file.size) {             const remaining_size = this.file.size - chunk_start;              \/\/ \u0437\u0430\u0433\u0440\u0443\u0436\u0430\u0435\u043c \u043a\u0443\u0441\u043a\u0430\u043c\u0438 default_chunk_size, \u043f\u043e\u0441\u043b\u0435\u0434\u043d\u0438\u0439 \u043a\u0443\u0441\u043e\u043a \u0434\u043e\u043f\u0443\u0441\u043a\u0430\u0435\u0442\u0441\u044f             \/\/ \u0432 \u043f\u043e\u043b\u0442\u043e\u0440\u0430 \u0440\u0430\u0437\u0430 \u0431\u043e\u043b\u044c\u0448\u0435, \u0447\u0435\u043c default_chunk_size             if(remaining_size &lt; 1.5 * this.uploader.settings.chunk_size) {                 chunk_size = remaining_size;             } else {                 chunk_size = this.uploader.settings.chunk_size;             }              const chunk = this.file.slice(chunk_start, chunk_start + chunk_size);             \/\/ XXX \u0441\u043e\u0445\u0440\u0430\u043d\u044f\u0442\u044c (start, end) \u0432 \u0441\u043b\u0430\u0439\u0441\u0435 - \u0433\u0440\u044f\u0437\u043d\u044b\u0439 \u0445\u0430\u043a, \u0430 \u0447\u0442\u043e \u0434\u0435\u043b\u0430\u0442\u044c?             chunk.start = chunk_start;             chunk.end = chunk_start + chunk_size;             while(true) {                 try {                     await this._upload_chunk(chunk);                     break;                 } catch(error) {                     console.log(`${this.file.name} upload error, retry in 5 seconds`);                     await new Promise(resolve => setTimeout(resolve, 5000));                 }             }              chunk_start += chunk_size;         }         await this.uploader.upload_complete(this.file, this.params);     }      _upload_chunk(chunk)     {         \/\/ \u042d\u0442\u0430 \u0444\u0443\u043d\u043a\u0446\u0438\u044f \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442 non-awaitable XMLHttpRequest, \u043f\u043e\u044d\u0442\u043e\u043c\u0443 \u043d\u0435 \u043c\u043e\u0436\u0435\u0442 \u0431\u044b\u0442\u044c async.         \/\/ \u041d\u043e \u043c\u044b \u0432\u044b\u0437\u044b\u0432\u0430\u0435\u043c \u0435\u0451 \u0441 await, \u0442\u0430\u043a \u0447\u0442\u043e \u0434\u043e\u043b\u0436\u043d\u044b \u0432\u0435\u0440\u043d\u0443\u0442\u044c promise.          const self = this;          return new Promise((resolve, reject) => {              const reader = new FileReader();             const xhr = new XMLHttpRequest();              xhr.upload.addEventListener(                 \"progress\",                 (e) => {                     if(e.lengthComputable) {                         const percentage = Math.round((e.loaded * 100) \/ e.total);                         self._update_progress(chunk, percentage);                     }                 },                 false             );              xhr.onreadystatechange = () => {                 if(xhr.readyState === xhr.DONE) {                     if(xhr.status === 200) {                         self._update_progress(chunk, 100);                         resolve(xhr.response);                     } else {                         reject({                             status: xhr.status,                             statusText: xhr.statusText                         });                     }                 }             };              xhr.onerror = () => {                 reject({                     status: xhr.status,                     statusText: xhr.statusText                 });             };              xhr.open('POST', this.uploader.settings.url);              const content_range = `bytes ${chunk.start}-${chunk.end - 1}\/${this.file.size}`;             xhr.setRequestHeader(\"Content-Range\", content_range);             xhr.setRequestHeader(\"Content-Type\", \"application\/octet-stream\");             xhr.setRequestHeader(this.uploader.settings.file_name_header, this.file.name);              reader.onload = (e) => {                 xhr.send(e.target.result);             };              reader.readAsArrayBuffer(chunk);             self._update_progress(chunk, 0);         });     }      _update_progress(chunk, percentage)     {         \/\/ \u0441\u0447\u0438\u0442\u0430\u0435\u043c \u043f\u0440\u043e\u0446\u0435\u043d\u0442\u044b \u0438 \u0432\u044b\u0437\u044b\u0432\u0430\u0435\u043c \u043c\u0435\u0442\u043e\u0434 progress         const chunk_start_percentage = chunk.start * 100 \/ this.file.size;         const chunk_end_percentage = chunk.end * 100 \/ this.file.size;         const upload_percentage = chunk_start_percentage + chunk.size * percentage \/ this.file.size;         this.uploader.progress(             this.file,             this.params,             chunk_start_percentage.toFixed(2),             chunk_end_percentage.toFixed(2),             upload_percentage.toFixed(2)         );     } }  \/\/ \u0442\u0438\u043f\u0430 \u044d\u043a\u0441\u043f\u043e\u0440\u0442\u0438\u0440\u0443\u0435\u043c FileUploader window.FileUploader = FileUploader;  })();<\/code><\/pre>\n<p>HTML \u0438 \u043e\u0441\u0442\u0430\u043b\u044c\u043d\u043e\u0439 Javascript:<\/p>\n<pre><code class=\"javascript\">&lt;h3>Upload Files&lt;\/h3> &lt;p>     &lt;button id=\"file-select\">Choose Files&lt;\/button> or drag and drop to the table below &lt;\/p> &lt;table id=\"file-list\">     &lt;thead>         &lt;tr>&lt;th>File name&lt;\/th>&lt;th>Size&lt;\/th>&lt;\/tr>     &lt;\/thead>     &lt;tbody>     &lt;\/tbody> &lt;\/table> &lt;template id=\"file-row\">     &lt;tr>&lt;td>&lt;\/td>&lt;td>&lt;\/td>&lt;\/tr> &lt;\/template> &lt;input type=\"file\" id=\"files-input\" multiple style=\"display:none\">  &lt;script>     const upload_complete_color = 'rgb(0,192,0,0.2)';     const chunk_complete_color = 'rgb(0,255,0,0.1)';      class Uploader extends FileUploader     {         constructor()         {             super({url: '\/api\/feedback\/upload'});              this.elem = {                 file_select:  document.getElementById(\"file-select\"),                 files_input:  document.getElementById(\"files-input\"),                 file_list:    document.getElementById(\"file-list\"),                 row_template: document.getElementById('file-row')             };             this.elem.tbody = this.elem.file_list.getElementsByTagName('tbody')[0];              this.row_index = 0;              this.set_event_handlers();         }          set_event_handlers()         {             const self = this;             this.elem.file_select.addEventListener(                 \"click\",                 () => { self.elem.files_input.click(); },                 false             );             this.elem.files_input.addEventListener(                 \"change\",                 () => { self.handle_files(self.elem.files_input.files) },                 false             );              function consume_event(e)             {                 e.stopPropagation();                 e.preventDefault();             }              function drop(e)             {                 consume_event(e);                 self.handle_files(e.dataTransfer.files);             }              this.elem.file_list.addEventListener(\"dragenter\", consume_event, false);             this.elem.file_list.addEventListener(\"dragover\", consume_event, false);             this.elem.file_list.addEventListener(\"drop\", drop, false);         }          progress(file, params, chunk_start_percentage, chunk_end_percentage, percentage)         {             params.progress_container.style.background = 'linear-gradient(to right, '                 + `${upload_complete_color} 0 ${percentage}%, `                 + `${chunk_complete_color} ${percentage}%<\/code><\/pre>\n<\/div>\n<\/div>\n<\/div>\n<\/div>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[],"tags":[],"class_list":["post-350649","post","type-post","status-publish","format-standard","hentry"],"_links":{"self":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/350649","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=350649"}],"version-history":[{"count":0,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/350649\/revisions"}],"wp:attachment":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=350649"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=350649"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=350649"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}