Повторять задержку цикла while до тех пор, пока вызовы ajax в нем не будут завершены - PullRequest
0 голосов
/ 15 марта 2019

Прежде чем объяснить, что я хочу сделать, вот MCV того, что я кодирую

$("#button").submit(function(event) {
  event.preventDefault();
  var formData = new FormData(this);
  var myString = $('#textarea').val();
  var myRegexp = /src="blob:([^'"]+)"/gm;
  match = myRegexp.exec(myString);

  var inProgress = 0;

  while (match != null) {

    var xhr = new XMLHttpRequest();
    addr = match[1];

    xhr.open('GET', 'blob:' + addr, true);
    xhr.responseType = 'blob';
    xhr.onload = function(e) {
      if (this.status == 200) {
        var myBlob = this.response;
        var data = new FormData();
        data.append('file', myBlob);

        $.ajax({
          url: "uploader.php",
          type: 'POST',
          data: data,
          contentType: false,
          processData: false,
          beforeSend: function() {
            inProgress++;
          },
          success: function(data) {
            myString = myString.replace("blob:" + addr, data);
          },
          error: function() {
            // error
          },
          complete: function() {
            --inProgress;
          }
        });
      } else {
        // error
      }
    };
    xhr.send();
    match = myRegexp.exec(myString);
  }

  if (!inProgress) {
    formData.set('textarea', myString);
    submitForm(formData);
  }
});

Итак, у меня есть текстовая область, и она содержит неизвестное количество объектов BLOB. Сначала я пытаюсь найти эти объекты BLOB с помощью regexp, а затем загружаю их на сервер, используя файл PHP с именем uploader.php. Как только файл загружен, он вернет URL-адрес загруженного файла, и я хочу заменить BLOB-URL-адрес URL-адресом загруженного файла в текстовой области, а затем отправить окончательное содержимое текстовой области на сервер для дальнейшей обработки.

Оказывается, что когда я запускаю код, только последний экземпляр регулярного выражения заменяется его загруженным URL-адресом. Остальные остаются такими, какими они были. Я подозреваю, что это потому, что цикл while не ожидает завершения запросов ajax. У меня была похожая проблема при попытке отправить форму, и я решил ее, следуя советам в этом ответе , но на этот раз я не знаю, как решить эту проблему.

Любая идея приветствуется

Обновление: Я пытался заставить Ajax работать синхронно, но мой браузер сказал, что он устарел и не работает.

Ответы [ 2 ]

1 голос
/ 16 марта 2019

Кажется, вам не нужно, чтобы оно было синхронным (и я не вижу случая, когда лучше сделать асинхронное действие синхронным), а нужно, чтобы оно было последовательным.

Можно сделать последовательные асинхронные действия с помощью обратных вызовов (которые перезаписываются как Promise и, в свою очередь, перезаписываются как асинхронные / ожидающие методы, но я буду простыми):

// myString is made global for simplicity
var myString;
function uploadBlob(myBlob, addr, callback) {
  var data = new FormData();
  data.append('file', myBlob);

  $.ajax({
    url: "uploader.php",
    type: 'POST',
    data: data,
    contentType: false,
    processData: false,
    success: function(data) {
      // file uploaded OK, replace the blob expr by the uploaded file url(data)
      myString = myString.replace("blob:" + addr, data);
      callback();
    },
    error: function() {
      // error, the uploaded most likely failed, we leave myString alone
      // or alternatively replace the blob expr by empty string
      // because maybe we dont want to post blob in the final form submit
      // uncomment if needed
      // myString = myString.replace("blob:" + addr, "");
      callback();
    }
  });
}

function getBlobAndUpload(addr, callback) {
  var xhr = new XMLHttpRequest();
  xhr.open('GET', 'blob:' + addr, true);
  xhr.responseType = 'blob';
  xhr.onload = function(e) {
    if (this.status == 200) {
      var myBlob = this.response;
      uploadBlob(myBlob, addr, callback);
    } else {
      // error, but callback anyway to continue processing
      callback();
    }
  };
  xhr.send();
}

function processAddresses(addresses, callback, current) {
  var index = current || 0;
  // all addresses processed?
  if (index >= addresses.length) {
    // yes no more address, call the callback function
    callback();
  } else {
    var addr = addresses[index];
    // once the get/upload is done the next address will be processed
    getBlobAndUpload(addr, function() {
      processAddresses(addresses, callback, index + 1);
    });
  }
}

$("#button").submit(function(event) {
  event.preventDefault();
  var formData = new FormData(this);
  var addresses = [];
  // initialize both localString and myString to the content of the textArea
  // localString will be used to extract addresses,
  // while myString will be mutated during the upload process
  var localString = myString = $('#textarea').val();
  var myRegexp = /src="blob:([^'"]+)"/gm;
  match = myRegexp.exec(localString);

  // collect all addresses first
  while (match != null) {

    addr = match[1];

    addresses.push(addr);
    match = myRegexp.exec(localString);
  }
  // initiate sequential processing of all addresses, and
  // pass the callback function triggering the form submit
  processAddresses(addresses, function() {
      // all the successfully uploaded blob exprs in my string should
      // be now replaced by the remote file url now (see commented part
      // in upload blob error for a variation of the feature
      formData.set('textarea', myString);
      submitForm(formData);
  });
});
1 голос
/ 16 марта 2019

Итак. Я сказал в комментариях, что вы можете использовать async / await, и дал ссылки. Теперь я попытаюсь научить вас, как работать с обещаниями и XMLHttpRequest.

Итак, первое. Я бы использовал свою собственную «библиотеку» (на самом деле это не библиотека, а всего лишь 3 новые команды) с именем PromiseReq, у которой есть XMLHttpsRequest, который возвращает Promises. Вам понадобятся две функции от него: sendToServer(config, data) и getServerFile(config). Они делают то, что подразумевают их имена. (sendToServer не так хорош в то время, но я улучшу это когда-нибудь позже). Они просто используют Обещания в качестве возврата. Они работают очень легко. Код @ Github

НО Он был разработан только для моего использования, поэтому он не очень гибкий (хотя я надеюсь, что когда-нибудь улучшу его).

Итак, нам нужно научиться использовать Обещания.

Во-первых, вам нужно знать, что такое Обещание и почему мы его используем.

Тогда вы можете создать такой как:

let pmq = new Promise((res,rej)=>{
// PROMISE BODY HERE
});

Здесь первое предупреждение. Обещания, сделанные таким образом, не поддерживают возвращение как способ решить Обещание! Вы должны использовать res()!

Некоторые функции просто возвращают их (например, fetch ()), и мы можем обработать их сразу после вызова функции.

Теперь pmq будет нашим обещанием. Вы можете использовать pmq.then(callback) для обработки того, что произойдет, если где-то в теле обещания будет res() call и pmq.catch(callback) для обработки того, что происходит, когда вызывается rej(). Помните, что .catch(cb) и .then(cb) возвращают Обещание, так что вы можете безопасно соединить более одного .then() и в конце добавить .catch(), и он будет обрабатывать отклонение от каждого из .then() с.

Например:

pmq = fetch("file.txt");
pmq.then(e=>console.log(e.json())).then(console.log).catch(console.error);

Там есть большая заметка.

Порядок, в котором будут происходить эти события.

Если, например, rP() ждет 1с, чем журналы "A", то разрешается, этот код:

let a = rP();
a.then(_=>console.log("B")).catch(console.error);
console.log("C");

приведет к:

C
A
B

В связи с этим для этого требуется async / await .

Для этого необходимо выполнить асинхронную функцию с ключевым словом async.

let fn = async ()=>{} 

Здесь вторая вещь. Асинхронные функции ВСЕГДА возвращают Promise. И это второй способ создать обещание. Вы просто не используете res(), rej() только return и throw.

Теперь мы можем позвонить внутрь fn():

let a = await rP().then(_=>console.log("B")).catch(console.error);
console.log("C");

и мы получим

A
B
C

Теперь. Как использовать его с XMLHttpRequest?

Вам нужно создать новый Promise с простым XMLHttpRequest внутри:

let xmlp = (type, path,data) => return new Promise((res,req)=>{
    let xhr = new XMLHttpsRequest();
    xhr.open(type, path, true); // true implies that is it asynchronous call
    //...
    xhr.send(data);
});

А теперь, когда решить? XMLHttpRequest имеет полезные свойства и события . Лучшим для нашего случая является onreadystatechange.

Вы можете использовать его так:

xhr.onreadystatechange = _=>{
    if(xhr.readyState === 4 && xhr.status === 200) // Everything went smoothly
        res(xhr.responseText);
    else if(shr.readyState === 4 && xhr.status !== 200) // Something went wrong!
        rej(xhr.status);
}

А затем, чтобы получить данные, вы можете либо

  1. Async/Await
// INSIDE ASYNC FUNCTION
let resData = await xmpl("GET", "path.txt", null).catch(console.error);
  1. .then()
let resData;
xmpl("GET", "path.txt", null).then(r=>{
    resData = r;
    // REST OF WHOLE FUNCTION TO ENSURE THAT resData HAS BEEN SET
})
.catch(console.error);

Вы также можете отправить данные с помощью xmpl().

xmpl("POST", "observer.php", "Data to send to observer.php!")
.then(whatToDoAfterSendFN);
/* 
to get such data in PHP you have to use 
$post = file_get_contents('php://input');
*/

Я знаю, что этот ответ немного сумбурный, но я понятия не имел, как его написать: P Извините.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...