4

我想知道我们如何使用 AngularJS 进行顺序 Web 服务调用?为简化起见,当我们得到第一个 web 服务的结果时,我们调用第二个。我已经找到了解决方案,但我不太喜欢它。我认为必须有更好的解决方案,这就是我发布问题的原因。这是我的代码(它有效)。

var uploadFile = function(file) {
    szUrl = globalServ.datas.szUrl+"transactions/"+globalServ.transactionId+"/file";
    var postData = {};
    postData['name'] = "MyFile"+file;
    postData['file'] = $scope.wsFiles[file];
    $http({
        method: 'POST', 
        url:szUrl, 
        headers: {'Content-Type': false, 'Authorization': globalServ.make_base_auth()}, 
        transformRequest: formDataObject,
        data:postData,
    }).success(function(response) {
        $scope.qFiles[file].resolve(file);//This will allow to call "then" method
    }).error(function (data, status, headers, config) {
        $scope.errorMsg = globalServ.getErrorMsg(status, data);
        $scope.filesAdded = false;
    });
}
$scope.uploadFiles = function() {
    delete $scope.errorStatus;

    $scope.qFiles = [];
    $scope.uploadOver = false;

    for(file in $scope.wsFiles) {
        var q1 = $q.defer();
        q1.promise.then(function(curFile) {
            if(curFile < $scope.wsFiles.length - 1) {
                uploadFile(curFile+1);
            } else {
                $scope.uploadOver = true;//We uploaded all contracts so now can stop to show loading button
            }
        });
        $scope.qFiles.push(q1);
    }
    uploadFile(0);//We start to upload contracts
};

欢迎所有批评。先感谢您。

4

3 回答 3

8

首先要接受,如果你要使用 Promise,你想在任何地方使用它们。它们已经提供了许多您尝试手动创建的构造。此外,$scope除非绝对必要,否则尽量避免向(或任何其他全局)添加内容。构建所有实用功能,使它们独立于范围和任何视图更新等。

上传文件

接下来要做的就是让uploadFilereturn 成为一个承诺。我们还希望它不使用 $scope 而是将它需要的所有信息作为参数。因此,我提出类似的建议:

function uploadFile(file) {
    szUrl = globalServ.datas.szUrl+"transactions/"+globalServ.transactionId+"/file";
    var postData = {};
    postData['name'] = "MyFile"+file.name;
    postData['file'] = file.data;
    return $http({  // <--------------------------- return the promise here
        method: 'POST', 
        url:szUrl, 
        headers: {'Content-Type': false, 'Authorization': globalServ.make_base_auth()}, 
        transformRequest: formDataObject,
        data:postData,
    }).then(function onSuccess(response) {  //<--- `.then` transforms the promise here
        return file
    }, function onError(response) {
        throw globalServ.getErrorMsg(response.status, response.data);
    });
}

其中 file 是表单的对象{name: 'file name', data: 'contents of file post'}

上传文件

对于上传文件,我们必须决定是并行上传还是顺序上传。并行通常会更快,但会使用更多资源(内存、带宽等)。如果你需要同时做很多其他的事情,你可能想把它们串联起来。您可能还想选择第三个选项(此处未介绍),即一次最多同时上传 5 个。

无论哪种方式,我们都将创建一个函数,该函数接受一个file对象数组并上传它们,然后返回一个在操作完成时解析的 Promise,或者如果失败则拒绝。

平行线

要并行执行,我们只需上传每个文件,然后使用$q.all等待所有上传完成。

function uploadFilesParallel(files) {
    var uploads = []
    for(var i = 0; i < files.length; i++) {
        uploads.push(uploadFile(files[i]))
    }
    return $q.all(uploads)
}

顺序的

顺序上传只是稍微复杂一点。原则上我们需要做的是让每个操作在开始之前等待前一个操作完成。为此,我们首先创建一个由已解决的承诺表示的虚拟初始操作。然后我们通过循环跟踪之前的操作,这样我们就可以在上传之前一直等待之前的操作。

function uploadFilesSequential(files) {
    var previous = $q.when(null) //initial start promise that's already resolved
    for(var i = 0; i < files.length; i++) {
        (function (i) {
            previous = previous.then(function () { //wait for previous operation
                return uploadFile(files[i])
            })
        }(i)) //create a fresh scope for i as the `then` handler is asynchronous
    }
    return previous
}

用法

最后是附加到范围的原始 uploadFiles 操作。此时,您可以做所有必要的事情来更新您的视图。请注意,到目前为止,我还没有触及任何全局变量。这样做的原因是为了确保多次调用uploadFilesSequentialuploadFilesParallel不会发生冲突并导致问题。

我没有对以下功能做出同样的保证。如果您并行调用它多次,则多个操作可能会导致奇怪的行为。

$scope.uploadFiles = function (parallel) {
    delete $scope.errorStatus;
    $scope.uploadOver = false;

    var files = [];

    for(file in $scope.wsFiles) {
        files.push({
            name: file,
            data: $scope.wsFiles[file]
        })
    }

    var uploadOperation
    if (parallel) {
        uploadOperation = uploadFilesParallel(files)
    } else {
        uploadOperation = uploadFilesSequential(files)
    }
    uploadOperation
        .then(function(file) {
            $scope.uploadOver = true;
        }, function (err) {
            $scope.errorMsg = err;
            $scope.filesAdded = false;
        });
};

显然,如果您始终使用并行或始终使用顺序上传,那么您可以稍微简化一下。请注意,这样做是多么简单,我们所要做的就是处理视图逻辑,而uploadFilesSequentialor的实际实现对uploadFilesParallel我们来说是完全隐藏的。正是这种封装对于使大型系统编程成为可能至关重要。

未来

JavaScript 的未来版本(EcmaScript 6)即将推出,这将使顺序版本变得更加容易。您将能够大致重写它:

var uploadFilesSequential = Q.async(function* (files) {
    for(var i = 0; i < files.length; i++) {
        yield uploadFile(files[i])
    }
})

whereyield是一个特殊的关键字,它等待一个 Promise 被解决。不幸的是,可能还需要几年的时间,您才能在现实世界的代码中使用它来在浏览器中运行。

于 2013-05-31T11:23:57.197 回答
0

Javascript 调用始终是异步的。但据我所知,使 javascript 调用同步的唯一方法是在第一个请求的成功/错误处理程序中调用新请求。

于 2013-05-31T10:23:18.410 回答
0

我想你想建立一个.then()链如下:

var uploadFile = function(index) {
    return $http({
        method: 'POST', 
        url: globalServ.datas.szUrl + "transactions/" + globalServ.transactionId + "/file",
        headers: {
            'Content-Type': false,
            'Authorization': globalServ.make_base_auth()
        },
        transformRequest: formDataObject,
        data: {
            name: "MyFile" + index,
            file: $scope.wsFiles[index]
        }
    }).error(function (data, status, headers, config) {
        $scope.errorMsg = globalServ.getErrorMsg(status, data);
        $scope.filesAdded = false;
    });
}
$scope.uploadFiles = function() {
    delete $scope.errorStatus;
    $scope.uploadOver = false;

    var p = $q.defer().resolve().promise;//Ready-resolved starter promise.

    function makeClosure(i) {
        return function() {
            return uploadFile(i);
        }
    }

    //Build a .then() chain
    for(var i=0; i<$scope.wsFiles.length; i++) {
        p = p.then(makeClosure(i));
    }

    //Add a terminal .then() to the chain.
    p.then(function() {
        $scope.uploadOver = true;//We uploaded all contracts so now can stop to show loading button
    });
};

我必须做出几个假设,所以这可能不是 100% 正确,但应该让你知道如何进行。

于 2013-05-31T11:18:56.903 回答