0

I need to return a rejected promise from a js function. I am using Angular $q as you can see. But it doesn't work.

In function getDBfileXHR, when the promise getDBfileXHRdeferred is rejected using getDBfileXHRdeferred.reject() I would to pass into the the error case of the function getDBfileXHR and run fallbackToLocalDBfileOrLocalStorageDB(). But it doesn't work.

Is there a syntax error? I am a bit new to promises.

this.get = function () {
    var debugOptionUseLocalDB = 0,
    prodata = [],
    serverAttempts = 0;

    if (debugOptionUseLocalDB) {
        return fallbackToLocalDBfileOrLocalStorageDB();
    }
    if (connectionStatus.f() === 'online') {
        console.log("Fetching DB from the server:");
        return getDBfileXHR(dbUrl(), serverAttempts)
        .then(function () { // success
            console.log('-basic XHR request succeeded.');
            return dbReadyDeferred.promise;
        }, function () { // error
            console.log("-basic XHR request failed, falling back to local DB file or localStorage DB...");
            return fallbackToLocalDBfileOrLocalStorageDB();
        });
        } 

}

function getDBfileXHR(url, serverAttempts) {
    var getDBfileXHRdeferred = $q.defer(),
    request = new XMLHttpRequest();
    if (typeof serverAttempts !== "undefined") serverAttempts++;
    request.open("GET", url, true); //3rd parameter is sync/async
    request.timeout = 2000;
    request.onreadystatechange = function () {      // Call a function when the state changes.
        if ((request.readyState === 4) && (request.status === 200 || request.status === 0)) {
            console.log('-we get response '+request.status+' from XHR in getDBfileXHR');
            var jsonText = request.responseText.replace("callback(", "").replace(");", "");
            if (jsonText === '') {
                console.error('-error : request.status = ' + request.status + ', but jsonText is empty for url=' + url);
                if (serverAttempts <= 2){
                    sendErrorEmail("BL: jsonText is empty, trying to reach server another time", 11);
                    getDBfileXHR(url, serverAttempts);
                    return;
                } else {
                    sendErrorEmail("BL: jsonText is empty and attempted to reach server more than twice", 14);
                    var alertPopup = $ionicPopup.alert({
                        title: 'Error '+"11, jsonText is empty",
                        template: "Sorry for the inconvenience, a warning email has been sent to the developpers, the app is going to restart.",
                        buttons: [{
                            text:'OK',
                            type: 'button-light'
                        }]
                    });
                    
                    getDBfileXHRdeferred.reject();
                }
            } else {

            }
        } else {
            console.error('-error, onreadystatechange gives : request.status = ' + request.status);
            getDBfileXHRdeferred.reject();
        }
    };
    if (url === "proDB.jsonp") {
        console.log("-Asking local proDB.json...");
    } else {
        console.log("-Sending XMLHttpRequest...");
    }

    request.send();
    return getDBfileXHRdeferred.promise;
}

Edit

I rewrote my function using this approach. It seems better and cleaner like this. How can I now handle the multiple attempts?

function getDBfileXHR(url, serverAttempts) {
    return new Promise(function (resolve, reject) {
        var request = new XMLHttpRequest();
        
        request.open("GET", url, true);                         request.timeout = 2000;
        var rejectdum;
        if (url === "proDB.jsonp") {
            console.log("-Asking local proDB.json...");
        } else {
            console.log("-Sending XMLHttpRequest...");
        }
        request.onload = function () {
            if ( (request.readyState === 4) && (request.status === 200 || request.status === 0) ) {
                console.log('-we get response '+request.status+' from XHR in getDBfileXHR');
                var jsonText = request.responseText.replace("callback(", "").replace(");", "");

                

                if (jsonText === '') {
                    console.error('-error : request.status = ' + request.status + ', but jsonText is empty for url=' + url);
                    sendErrorEmail("BL: jsonText is empty, trying to reach server another time", 11);
                    sendErrorEmail("BL: jsonText is empty and attempted to reach server more than twice", 14);
                    var alertPopup = $ionicPopup.alert({
                        title: 'Error '+"11, jsonText is empty",
                        template: "The surfboard database could not be updated, you won't see the new models in the list, sorry for the inconvenience.",
                        buttons: [{
                            text:'OK',
                            type: 'button-light'
                        }]
                    });
                    console.log('oui on passe rejectdum')
                    rejectdum = 1;
                    reject({
                        status: this.status,
                        statusText: request.statusText
                    });
                    
                } else {
                    var parsedJson;
                    try {
                        parsedJson = JSON.parse(jsonText);
                    } catch (e) {
                        console.warn("Problem when trying to JSON.parse(jsonText) : ");
                        console.warn(e);
                        console.warn("parsedJson :");
                        console.warn(parsedJson);

                    }
                    if (parsedJson) {
                        var prodata = jsonToVarProdata(parsedJson);

                        
                        
                        console.log('-writing new prodata to localStorage');                            
                        console.log('last line of prodata:' + prodata[prodata-1]);
                        storageService.persist('prodata', prodata);
                        storageService.store('gotANewDB', 1);
                    }
                    resolve(request.response);
                    dbReadyDeferred.resolve();
                }
            }
        };
        request.onerror = function () {
            reject({
                status: this.status,
                statusText: request.statusText
            });
        };

        request.send();
        
    });
}

Is it a clean way to do this to do several attempts :

return getDBfileXHR(dbUrl(), serverAttempts)
.then(function () { // success
    console.log('-basic XHR request succeeded.');
    return dbReadyDeferred.promise;
})
.catch(function (){
    if (typeof serverAttempts !== "undefined") serverAttempts++;
    console.log('on passe dans le catch, serverAttempts = ', serverAttempts)
    if (serverAttempts < 2) {
        return getDBfileXHR(dbUrl(), serverAttempts)
        .then(function () { // success
            console.log('-basic XHR request succeeded.');
            return dbReadyDeferred.promise;
        })
        .catch(function (){
            console.log("-basic XHR request failed, falling back to local DB file or localStorage DB...");
            return fallbackToLocalDBfileOrLocalStorageDB();
        })
    } else {
        console.log("-basic XHR request failed, falling back to local DB file or localStorage DB...");
        return fallbackToLocalDBfileOrLocalStorageDB();
    }
})
halfer
  • 19,824
  • 17
  • 99
  • 186
Louis
  • 2,548
  • 10
  • 63
  • 120
  • Are you sure that the .reject() statement is getting called? – Sidharth Panwar Jan 17 '17 at 17:28
  • Yes, please provide confirmation that the `.reject()` method is being executed. Also, some quick observations: 1. I don't see a call to `getDBfileXHRdeferred.resolve();`, I think you want to do that in the empty `else` clause. 2. When repeating the attempt, you should `return getDBfileXHR(url, serverAttempts);`, not `return;` -- otherwise, you will never return the outcome of subsequent requests. – GPicazo Jan 17 '17 at 17:42
  • I am sure that the promise is rejected and the error case is not executed, because I tried to put `jsonText = '';` just before `if (jsonText === '') {` Sorry about the `.resolve()` case, I removed it from the code for simplicity. Ok thank you for your advice on repeating the attempts, I was not sure how to code this. I am fixing this now : so I should replace `getDBfileXHR(url, serverAttempts); return;` by `return getDBfileXHR(url, serverAttempts);` is that right ? – Louis Jan 17 '17 at 18:22
  • is it because I declare `var getDBfileXHRdeferred = $q.defer()` inside the getDBfileXHR function, and it is reset each time ? – Louis Jan 17 '17 at 19:26
  • And also it seems that, as soon as the promise is rejected (`getDBfileXHRdeferred.reject()`), the error case of the function `return getDBfileXHR(dbUrl(), serverAttempts) .then(..., ...)` is executed. Is that right ? – Louis Jan 17 '17 at 19:39

1 Answers1

0

if you remove the code to retry (twice?) on failure your code would possibly work (haven't looked into that) -

the issue is, the only promise your calling code gets is that of the first attempt. If the first attempt fails, that promise is never resolved or rejected

You need to resolve the promise with the promise returned by getDBfileXHR(url, serverAttempts); - so, something like

if (serverAttempts <= 2){
    sendErrorEmail("BL: jsonText is empty, trying to reach server another time", 11);
    getDBfileXHRdeferred.resolve(getDBfileXHR(url, serverAttempts));
    return;
} else {

Because if promise(1) resolves to a rejected promise(2), the result is that promise(1) rejects with the rejection value of promise(2)

This is how native Promises, and many many Promise/A+ compliant libraries work, so this should be the case with $.defer if it follows the Promise/A+ spec

Jaromanda X
  • 53,868
  • 5
  • 73
  • 87