【问题标题】:Make a while loop delay repeating until ajax calls in it are complete重复一个while循环延迟,直到其中的ajax调用完成
【发布时间】:2019-08-06 20:40:24
【问题描述】:

在我解释我想要做什么之前,这是我正在编码的 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 对象,然后使用名为 uploader.php 的 PHP 文件将它们上传到服务器。文件上传后,会返回上传文件的URL,我想在文本区用上传文件的URL替换BLOB URL,然后将文本区的最终内容提交给服务器做进一步处理。

事实证明,当我运行代码时,只有正则表达式的最后一个实例被其上传的 URL 替换。其他人保持原样。我怀疑这是因为 while 循环不等待 ajax 请求完成。我在尝试提交表单时遇到了类似的问题,我按照answer 中的建议解决了这个问题,但这次我不知道如何解决这个问题。

任何想法都值得赞赏

更新:我尝试让 ajax 同步工作,但我的浏览器说它已被弃用并且无法正常工作。

【问题讨论】:

  • 你试过async/awaitfetch吗?我不使用 jQuery(我认为纯 JS 是最好的)所以我不知道 jQuery 的 $.ajax 是否适用于 Promises。
  • @SkillGG - OP 同时使用 vanilla 和 jQuery :-/)
  • 没有vanila就没有jQuery :)
  • 我会使用 fetch() 和 XMLHttpRequest(带有 Header)稍微改变以返回 Promise 并等待它等待它。但我不会使用 $.ajax,所以我不知道这个解决方案是否适合 OP。

标签: javascript jquery ajax while-loop


【解决方案1】:

您似乎并不真的需要它是同步的(而且我看不到让异步操作看起来同步更好的情况),而只需要它是顺序的。

可以通过使用回调使异步操作按顺序进行(可重写为 Promise,进而可重写为 async/await 方法,但我会保持简单):

// 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);
  });
});

【讨论】:

  • 我需要一些时间来分析你的答案并理解它。感谢您花时间写一个答案。
  • 答案不会像现在这样起作用,我正在想办法处理myString
  • 现在应该可以了。我想我已经理解了 myString 是如何被使用并使用不同的局部变量来进行 blob 解析的。也不再需要进行中的 var。
  • 谢谢。我尝试了您的方法,尽管我必须解决一些并没有真正改变代码逻辑的问题。例如,数组地址没有在 $('#button').submit() 中定义,或者 XHR 没有在 getBlobAndUpload() 中定义。我还将 callBack() 更改为 callback() ,这似乎是预期的。但它没有用。它仍然只替换 BLOB 对象的最后一个实例并忽略其余的实例。 :(
  • 我发现了问题。 uploadBlob() 需要三个变量:myBlob、addr、callback。在此之前,addr 没有更新,它一遍又一遍地替换相同的东西。
【解决方案2】:

所以。我在 cmets 中说过,您可以使用 async/await,并提供链接。现在我将尝试教你如何使用 Promise 和 XMLHttpRequest。

所以第一件事。我会使用我自己的名为 PromiseReq 的“库”(不是真正的库,只是 3 个新命令),它具有返回 Promises 的 XMLHttpsRequest。 您将需要其中的两个功能: sendToServer(config, data)getServerFile(config)。他们做他们名字所暗示的事情。(sendToServer当时不太好,但我稍后会改进它)。他们只是使用 Promises 作为回报。他们以非常简单的方式工作。 Code @ Github

但是它是为我的使用而设计的,所以它不是很灵活(虽然我希望我会改进它)。

所以我们需要学习如何使用 Promises。

首先您需要知道Promise 是什么以及我们为什么要使用它。

然后你可以像这样创建一个:

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

这里是第一个警告。以这种方式做出的承诺不支持将 return 作为解决 Promise 的一种方式!你必须使用res()

有些函数只是返回它们(例如fetch()),我们可以在调用函数后立即处理它们。

现在pmq 将成为我们的承诺。 您可以使用pmq.then(callback) 来处理如果promise 主体中的某处是res() 调用和pmq.catch(callback) 来处理调用rej() 时会发生的情况。请记住,.catch(cb).then(cb) 返回一个 Promise,因此您可以安全地链接多个 .then() 并在最后添加 .catch(),它将处理来自每个 .then()s 的拒绝。

例如:

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(),只使用returnthrow

现在我们可以在fn()内部调用:

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

我们会得到我们的

A
B
C

现在。如何与 XMLHttpRequest 一起使用?

你需要用简单的 XMLHttpRequest 创建新的 Promise:

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 有有用的event properties and events。最适合我们的情况是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 对不起。

【讨论】:

  • 我需要一些时间来分析你的答案并理解它。感谢您花时间写一个答案。
  • 这是一个很好很详细的答案。但是恐怕我目前对JavaScript的了解还不够充分,无法完全理解和欣赏它。 :(虽然我从中学到了很多东西,但我肯定会在以后多次返回它。谢谢。
猜你喜欢
  • 2022-10-12
  • 1970-01-01
  • 1970-01-01
  • 2013-08-31
  • 1970-01-01
  • 2016-09-11
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多