Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to make sequentially Rest webservices calls with AngularJS?

I would like to know how we can make sequential webservices call with AngularJS? To simplify, when we got the result of the first webservices we call the second one. I already found a solution but I don't really like it. I think there must be a better solution, that's why I post my question. This is my code (and it works).

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

All critical are welcome. Thank you in advance.

like image 573
M07 Avatar asked Feb 16 '23 09:02

M07


1 Answers

Start by accepting that if you're going to use promises, you want to use them everywhere. They already provide many of the constructs that you're trying to create by hand. In addition, try and avoid adding things to $scope (or any other global) unless absolutely necessary. Build all your utility functions so that they're independent of the scope and of any view updating etc.

Upload File

The next thing to do is to make uploadFile return a promise. We also want it to not use $scope but rather take all the information it needs as an argument. I therefore propose something like:

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

where file is an object of the form {name: 'file name', data: 'contents of file post'}

Upload Files

For upload files, we have to decide whether we want them to be uploaded in parallel or sequentially. Parallel will usually be faster, but will use more resources (memory, bandwidth etc.). You may want to do them in series if you need to do lots of other things at the same time. You may also want to pick a third option (not covered here) of uploading say a maximum of 5 at a time in parallel.

Either way, we're going to create a function that takes an array of file objects and uploads them, then returns a promise that is resolved when the operation completes, or rejected if it fails.

Parallel

To do it in parallel, we just upload each file and then use $q.all to wait for all the uploads to finish.

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

Sequential

Uploading sequentially is just a little more complicated. In principle what we need to do is have each operation wait for the previous operation to complete before starting. To do this we first create a dummy initial operation that is represented by an already resolved promise. We then keep track of the previous operation through the loop so that we can always wait for the previous operation before uploading.

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
}

Usage

Finally your original uploadFiles operation that was attached to the scope. At this point you can do all the necessary stuff to update your view. Note how I haven't touched any globals up until this point. The reason for this is to ensure that multiple calls to uploadFilesSequential or uploadFilesParallel won't conflict and cause problems.

I have not made the same guarantees about the following function. If you call it multiple times in parallel, the multiple operations may cause strange behavior.

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

Clearly if you're always using parallel or always using sequential uploading then you can simplify that slightly. Note how simple it is to do this, all we have to do is handle the view logic and the actual implementation of uploadFilesSequential or uploadFilesParallel is completely hidden form us. It's this encapsulation that is essential to making programming large systems possible.

The Future

A future version of JavaScript is coming (EcmaScript 6) that will make the sequential version much easier. You'll be able to re-write it approximately like:

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

Where yield is a special keyword that waits for a promise to be resolved. Unfortunately it'll probably be another couple of years before that's something you can use in real world code to be run in the browser.

like image 103
ForbesLindesay Avatar answered May 09 '23 00:05

ForbesLindesay