6 сентябр 2025

Qayta boshlanuvchi fayl yuklash

fetch metodi bilan faylni yuklash ancha oson.

Ulanish uzilganidan keyin yuklashni qanday davom ettirish mumkin? Buning uchun o’rnatilgan opsiya yo’q, lekin buni amalga oshirish uchun kerakli qismlar bizda bor.

Qayta boshlanuvchi yuklashlar yuklash jarayonini ko’rsatish bilan birga kelishi kerak, chunki biz katta fayllarni kutamiz (agar davom ettirish kerak bo’lsa). fetch yuklash jarayonini kuzatishga imkon bermagani uchun, biz XMLHttpRequest dan foydalanamiz.

Unchalik foydali bo’lmagan progress eventi

Yuklashni davom ettirish uchun ulanish uzilgunga qadar qancha yuklangani ma’lum bo’lishi kerak.

Yuklash jarayonini kuzatish uchun xhr.upload.onprogress mavjud.

Afsuski, bu bizga yuklashni davom ettirishda yordam bermaydi, chunki u ma’lumotlar yuborilganda ishga tushadi, lekin ular server tomonidan qabul qilindimi? Brauzer buni bilmaydi.

Ehtimol, u mahalliy tarmoq proksi tomonidan buferga olingan, yoki masofaviy server jarayoni shunchaki o’lib qoldi va ularni qayta ishlay olmadi, yoki u o’rtada yo’qoldi va qabul qiluvchiga yetmadi.

Shuning uchun bu event faqat chiroyli progress bar ko’rsatish uchun foydali.

Yuklashni davom ettirish uchun server tomonidan qabul qilingan baytlarning aniq sonini bilishimiz kerak. Va buni faqat server aytishi mumkin, shuning uchun biz qo’shimcha so’rov qilamiz.

Algoritm

  1. Birinchidan, yuklashimiz kerak bo’lgan faylni noyob identifikatsiya qilish uchun fayl id’sini yarating:

    let fileId = file.name + '-' + file.size + '-' + file.lastModified;

    Bu yuklashni davom ettirish uchun kerak, serverga nimani davom ettirayotganimizni aytish uchun.

    Agar nom yoki hajm yoki oxirgi o’zgarish sanasi o’zgarsa, boshqa fileId bo’ladi.

  2. Serverga so’rov yuboring, u allaqachon qancha baytga ega ekanligini so’rang, masalan:

    let response = await fetch('status', {
      headers: {
        'X-File-Id': fileId
      }
    });
    
    // Server shuncha baytga ega
    let startByte = +await response.text();

    Bu server fayl yuklashlarini X-File-Id header bo’yicha kuzatadi deb taxmin qiladi. Server tomonida amalga oshirilishi kerak.

    Agar fayl hali serverda mavjud bo’lmasa, server javobi 0 bo’lishi kerak

  3. Keyin, faylni startByte dan yuborish uchun Blob metodining slice dan foydalanishimiz mumkin:

    xhr.open("POST", "upload", true);
    
    // Fayl id'si, server qaysi faylni yuklayotganimizni bilishi uchun
    xhr.setRequestHeader('X-File-Id', fileId);
    
    // Davom ettirayotgan bayt, server davom ettirayotganimizni bilishi uchun
    xhr.setRequestHeader('X-Start-Byte', startByte);
    
    xhr.upload.onprogress = (e) => {
      console.log(`${startByte + e.total} dan ${startByte + e.loaded} yuklandi`);
    };
    
    // file input.files[0] yoki boshqa manbadan bo'lishi mumkin
    xhr.send(file.slice(startByte));

    Bu yerda biz serverga fayl id’sini X-File-Id sifatida yuboramiz, shuning uchun u qaysi faylni yuklayotganimizni biladi, va boshlang’ich baytni X-Start-Byte sifatida yuboramiz, shuning uchun u biz uni dastlab emas, balki davom ettirayotganimizni biladi.

    Server o’z yozuvlarini tekshirishi va agar o’sha faylning yuklashi bo’lgan bo’lsa va joriy yuklangan hajm aynan X-Start-Byte ga teng bo’lsa, ma’lumotlarni unga qo’shishi kerak.

Mana Node.js da yozilgan mijoz va server kodi bilan demo.

Bu saytda faqat qisman ishlaydi, chunki Node.js Nginx nomli boshqa server orqasida joylashgan bo’lib, u yuklashlarni buferga oladi va ularni to’liq tugagandan keyin Node.js ga uzatadi.

Lekin siz uni yuklab olishingiz va to’liq namoyish uchun mahalliy ravishda ishga tushirishingiz mumkin:

Natija
server.js
uploader.js
index.html
let http = require("http");
let static = require("node-static");
let fileServer = new static.Server(".");
let path = require("path");
let fs = require("fs");
let debug = require("debug")("example:resume-upload");

let uploads = Object.create(null);

function onUpload(req, res) {
  let fileId = req.headers["x-file-id"];
  let startByte = +req.headers["x-start-byte"];

  if (!fileId) {
    res.writeHead(400, "No file id");
    res.end();
  }

  // biz "hech bir joyda" fayllarni joylashtiramiz
  let filePath = "/dev/null";
  // Buning o'rniga haqiqiy yo'lni ishlatishi mumkin, masalan.
  // let filePath = path.join('/tmp', fileId);

  debug("onUpload fileId: ", fileId);

  // yangi yuklashni ishga tushiring
  if (!uploads[fileId]) uploads[fileId] = {};
  let upload = uploads[fileId];

  debug("bytesReceived:" + upload.bytesReceived + " startByte:" + startByte);

  let fileStream;

  // agar startByte 0 bo'lsa yoki o'rnatilmagan bo'lsa, yangi fayl yarating, aks holda hajmini tekshiring va mavjud faylga qo'shing
  if (!startByte) {
    upload.bytesReceived = 0;
    fileStream = fs.createWriteStream(filePath, {
      flags: "w",
    });
    debug("New file created: " + filePath);
  } else {
    // Ishonch hosil qilish uchun diskdagi fayl hajmini ham tekshirishimiz mumkin
    if (upload.bytesReceived != startByte) {
      res.writeHead(400, "Wrong start byte");
      res.end(upload.bytesReceived);
      return;
    }
    // mavjud faylga qo'shing
    fileStream = fs.createWriteStream(filePath, {
      flags: "a",
    });
    debug("File reopened: " + filePath);
  }

  req.on("data", function (data) {
    debug("bitlar qabul qilindi", upload.bytesReceived);
    upload.bytesReceived += data.length;
  });

  // so'rov tanasini faylga yuboring
  req.pipe(fileStream);

  // so'rov tugaganda va uning barcha ma'lumotlari yoziladi
  fileStream.on("close", function () {
    if (upload.bytesReceived == req.headers["x-file-size"]) {
      debug("Yuklash yakunlandi");
      delete uploads[fileId];

      // bu yerda yuklangan fayl bilan boshqa biror narsa qilish mumkin

      res.end("Success " + upload.bytesReceived);
    } else {
      // ulanish yo'qolsa, biz tugallanmagan faylni atrofida qoldiramiz
      debug("Fayl tugallanmagan, toʻxtatilgan " + upload.bytesReceived);
      res.end();
    }
  });

  // kiritish-chiqarish xatosi bo'lsa - so'rovni yakunlang
  fileStream.on("error", function (err) {
    debug("fileStream xatoligi");
    res.writeHead(500, "Fayl xatoligi");
    res.end();
  });
}

function onStatus(req, res) {
  let fileId = req.headers["x-file-id"];
  let upload = uploads[fileId];
  debug("onStatus fileId:", fileId, " upload:", upload);
  if (!upload) {
    res.end("0");
  } else {
    res.end(String(upload.bytesReceived));
  }
}

function accept(req, res) {
  if (req.url == "/status") {
    onStatus(req, res);
  } else if (req.url == "/upload" && req.method == "POST") {
    onUpload(req, res);
  } else {
    fileServer.serve(req, res);
  }
}

// -----------------------------------

if (!module.parent) {
  http.createServer(accept).listen(8080);
  console.log("Server 8080 portida ishlamoqda");
} else {
  exports.accept = accept;
}
class Uploader {
  constructor({ file, onProgress }) {
    this.file = file;
    this.onProgress = onProgress;

    // faylni noyob identifikatsiya qiladigan fileId yaratish
    // agar foydalanuvchi sessiya identifikatori bo'lsa, uni ham qo'shib yanada noyob qilish mumkin
    this.fileId = file.name + "-" + file.size + "-" + file.lastModified;
  }

  async getUploadedBytes() {
    let response = await fetch("status", {
      headers: {
        "X-File-Id": this.fileId,
      },
    });

    if (response.status != 200) {
      throw new Error(
        "Yuklangan baytlarni olish mumkin emas: " + response.statusText
      );
    }

    let text = await response.text();

    return +text;
  }

  async upload() {
    this.startByte = await this.getUploadedBytes();

    let xhr = (this.xhr = new XMLHttpRequest());
    xhr.open("POST", "upload", true);

    // fayl id'sini yuborish, server qaysi faylni davom ettirishni bilishi uchun
    xhr.setRequestHeader("X-File-Id", this.fileId);
    // davom ettirilayotgan baytni yuborish, server davom ettirilayotganini bilishi uchun
    xhr.setRequestHeader("X-Start-Byte", this.startByte);

    xhr.upload.onprogress = (e) => {
      this.onProgress(this.startByte + e.loaded, this.startByte + e.total);
    };

    console.log("faylni yuborish, boshlash nuqtasi:", this.startByte);
    xhr.send(this.file.slice(this.startByte));

    // qaytarish:
    //   yuklash muvaffaqiyatli bo'lsa true,
    //   to'xtatilgan bo'lsa false
    // xatolik holatida exception tashlash
    return await new Promise((resolve, reject) => {
      xhr.onload = xhr.onerror = () => {
        console.log(
          "yuklash tugashi holati:" + xhr.status + " matn:" + xhr.statusText
        );

        if (xhr.status == 200) {
          resolve(true);
        } else {
          reject(new Error("Yuklash muvaffaqiyatsiz: " + xhr.statusText));
        }
      };

      // onabort faqat xhr.abort() chaqirilganda ishga tushadi
      xhr.onabort = () => resolve(false);
    });
  }

  stop() {
    if (this.xhr) {
      this.xhr.abort();
    }
  }
}
<!DOCTYPE html>

<script src="uploader.js"></script>

<form
  name="upload"
  method="POST"
  enctype="multipart/form-data"
  action="/upload"
>
  <input type="file" name="myfile" />
  <input
    type="submit"
    name="submit"
    value="Yuklash (Qayta avtomatik ishga tushadi)"
  />
</form>

<button onclick="uploader.stop()">Yuklashni to'xtatish</button>

<div id="log">Progress indication</div>

<script>
  function log(html) {
    document.getElementById("log").innerHTML = html;
    console.log(html);
  }

  function onProgress(loaded, total) {
    log("progress " + loaded + " / " + total);
  }

  let uploader;

  document.forms.upload.onsubmit = async function (e) {
    e.preventDefault();

    let file = this.elements.myfile.files[0];
    if (!file) return;

    uploader = new Uploader({ file, onProgress });

    try {
      let uploaded = await uploader.upload();

      if (uploaded) {
        log("muvaffaqiyatl");
      } else {
        log("to'xtatildi");
      }
    } catch (err) {
      console.error(err);
      log("error");
    }
  };
</script>

Ko’rib turganingizdek, zamonaviy tarmoq metodlari o’z imkoniyatlari bo’yicha fayl menejerlariga yaqin – header’lar ustidan nazorat, progress ko’rsatkichi, fayl qismlarini yuborish va hokazo.

Biz qayta boshlanuvchi yuklash va yana ko’p narsalarni amalga oshirishimiz mumkin.

O'quv qo'llanma xaritasi

Izohlar

izoh berishdan oldin buni o'qing…
  • Agar sizda nimani yaxshilash kerakligi haqida takliflaringiz bo'lsa - iltimos, GitHub muammosini yuboring yoki izoh berish o'rniga so'rov yuboring.
  • Agar siz maqolada biror narsani tushunolmasangiz - iltimos, batafsilroq ma'lumot bering.
  • Bir nechta so'z so'zlarini kiritish uchun <code> yorlig'ini ishlating, bir nechta satrlar uchun - ularni <pre> yorlig'i bilan o'rab qo'ying, 10 satrdan ortiq bo'lsa - sandbox (plnkr, jsbin, codepen…)