【问题标题】:How to make sequentially Rest webservices calls with AngularJS?如何使用 AngularJS 按顺序进行 Rest web 服务调用?
【发布时间】:2013-06-01 01:37:05
【问题描述】:

我想知道我们如何使用 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
};

欢迎所有批评。提前谢谢你。

【问题讨论】:

    标签: web-services angularjs promise sequential


    【解决方案1】:

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

    上传文件

    接下来要做的是让uploadFile 返回一个承诺。我们还希望它不使用 $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 对象的数组并上传它们,然后返回一个承诺,该承诺在操作完成时被解析,如果失败则被拒绝。

    平行

    要并行执行,我们只需上传每个文件,然后使用$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;
            });
    };
    

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

    未来

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

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

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

    【讨论】:

    • 我测试了你的顺序函数并且同时调用了所有的 web 服务......(我在开发人员工具中看到了它,并且只上传了第一个文件)。但不错的尝试,我喜欢你的想法。
    • (我给你+1的想法)。只是一些小错误:uploadOperation = uploadFilesSequential(file s) and } ) (i) //create a new scope for i as t
    • @M07 我错过了return 声明:
    • previous = previous.then(function () { //wait for previous operation return uploadFile(files[i]) //wait for current operation })
    【解决方案2】:

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

    【讨论】:

    • 这将使调用“顺序”而不是“同步”。实际上可以在 JavaScript 中发出异步 HTTP 请求,但不建议这样做,因为如果这样做,您的网站将挂起/变得无响应,直到请求完成。如果请求很大,可能需要几秒钟。
    • 这里的目标实际上是使请求顺序而不是同步,所以你是对的。问题是如何并行处理大量调用。这就是造成更复杂的流量控制问题的原因。
    【解决方案3】:

    我想你想建立一个.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% 正确,但应该让您知道如何进行。

    【讨论】:

    • 您遇到了问题,因为.then 处理程序中的i 将始终是i 变量中的最后一个。原因是您的 for 循环在异步方法得到处理之前就结束了。据我了解,带有 let 的 ES6 可以解决此问题。
    • 呵呵,初学者的错误!谢谢福布斯。我还没有进入let,所以用传统的闭包修复它。
    • 是的,这行得通。就我个人而言,我更喜欢“自我调用”功能(请参阅我的答案),但任何一种解决方案都同样有效。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-01-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多