【问题标题】:How to know if XMLHttpRequest has finished?如何知道 XMLHttpRequest 是否已经完成?
【发布时间】:2020-02-06 02:09:14
【问题描述】:

我正在使用表单字段上传多个文件

<input type="file" accept="image/jpeg, image/png" multiple 

一个循环将所有文件发送到上传函数

       // Upload photo function
            var upload = function (photo, callback) {
                var formData = new FormData();
                formData.append('photo', photo);
                var request = new XMLHttpRequest();
                request.onreadystatechange = function() {
                    if (request.readyState === 4) {
                        callback(request.response);
                    }
                }
                request.open('POST', 'upload.php');
                request.responseType = 'json';
                request.send(formData);
            };  


    for (var i = 0; i < files.length; i++) {
        upload(resizedFile, function (response) { });
    }

如果所有文件都上传成功,想重定向..

//all files finished, go to ...
if(response !== null && typeof response !== 'object') var response = JSON.parse(response);
window.location.href = "my-new-url.php?id="+response.id;

我的问题,重定向是在所有上传完成之前完成的。如何让脚本等待所有 xmlhttprequests 完成?


编辑

我把上传功能改成这样,用 jQuery 上传。该函数在for循环中被多次调用,那么如何等待所有循环/jquery调用完成,然后重定向?

function upload (photo, callback) {
        var fd = new FormData();
        fd.append('photo',photo);

        return $.ajax({
            url: 'upload.php',
            type: 'post',
            data: fd,
            contentType: false,
            processData: false,
            success: function(response){

            },
        });
    };

【问题讨论】:

  • 为每个请求创建一个promise,然后使用Promise.all()等待所有请求。
  • 怎么做?

标签: javascript jquery upload xmlhttprequest photo


【解决方案1】:

您可以在回调中增加一个全局计数器变量。当它达到数组的长度时,所有的请求都完成了。

var completed = 0;
for (var i = 0; i < files.length; i++) {
    upload(resizedFile, function (response) {
        if (++completed == files.length) {
            window.location = redirect_url;
        }
    });
}

【讨论】:

    【解决方案2】:

    首先,我会承诺上传功能。使用 JQuery 会更短,因为 $.ajax 返回一个 thenable 对象,但使用老式的香草 JS,它看起来像这样:

    var upload = function (photo) {
        return new Promise(function(resolve, reject) {
            var request = new XMLHttpRequest();
    
            request.onload = function() {
                if (request.status >= 200 && request.status < 300) {
                    resolve(request.response);
                } else {
                    reject(request.statusText);
                }
            };      
            request.onerror = function() { reject(request.statusText) }
            request.open('POST', 'upload.php');
            request.responseType = 'json';
    
            var formData = new FormData();
            formData.append('photo', photo);
            request.send(formData);
        })
    }
    

    另请参阅:http://ccoenraets.github.io/es6-tutorial-data/promisify/

    然后我会将这些承诺收集到一个数组中:

    var uploads = files.map(function(file) { return upload(resizeFile(file)); })
    

    (或者像你一样简单地迭代并将它们推送到一个数组中)

    然后您可以使用此代码来处理所有上传的完成:

    Promise.all(uploads)
       .then(function(results) { /* do the redirect */ })
       .catch(function(error) { console.log(error); });
    

    另见:https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/all

    注意:为什么其他答案中建议的计数错误?因为它不处理任何请求失败时的条件。您将无法检测到这种情况,并且您永远不会达到满足的条件。 Promise(和 async/await)是处理异步操作的正确方式,例如 ajax 请求。

    [更新]

    这里是使用最新 JS 语言特性的 JQuery 方法:

    var upload = (photo) => {
       photo = resizeFile(photo);
    
       let formData = new FormData();
       formData.append('photo', photo);
       return $.ajax({
         type: 'POST',
         url: 'upload.php',
         data: formData
       }) // check other the options of ajax method, you might need them
    }
    
    let uploads = files.map(upload);
    
    $.when.apply($,uploads)
      .done(_ => { /* do the redirect */ })
      .fail(e => { /* handle error */ })
    

    后半部分可以写成async/await语法:

    async somefunction(){ 
    
      // rest of the code
    
      try {
         await $.when.apply($,uploads)
         /* do the redirect */
      } catch(ex) {
         /* handle error */
      }
    }
    

    注意,$.ajax 返回 Deferred,这是一个 JQuery 对象,兼容 Promise。

    【讨论】:

    • 感谢您的回答和解释.. jQuery ajax 会是什么样子?它会比承诺的更短/更简单吗?
    • 请参阅我上面的编辑问题..我已将上传功能更改为 jquery ..但是如果所有上传都已完成,我如何在 for 循环中检查..然后重定向?!跨度>
    • @Aldee 请仔细阅读我的整个更新。我发布的方法返回Deferred(如Promise)。我已将所有上传映射到一个数组中。这意味着我有一个 Defered-s 数组。现在,$.when 的最后一部分将所有这些组合在一个 Deferred 中,这完全失败了。另一方面,您可以轻松地在一个请求中上传多个文件。