简体   繁体   中英

javascript trouble with returning a promise- the returned promise is not executing

I have a function that looks like this

this.getToken = function() {
    if (token === null) {
        token = getAccessTokenAsync("username", "password");
        lastTokenTime = getTokenExpiryAsync();
    }
}

this function will call getAccessTokenAsync which will make a request to my web server with xhr. This looks like this:

getAccessTokenAsync = function (username, password) {
    var serializedData = {
        username: username, password: password,
    };

    return new WinJS.Promise(function (complete) {
        WinJS.xhr({
            type: "post",
            url: "http://127.0.0.1:8080/authenticate/login",
            responseType: "json",
            data: JSON.stringify(serializedData)
        }).done(
            function complete(result){
                return JSON.parse(result.responseText);
            }
        );
    })
}

I would expect token to now store a promise inside of it. Which when we then call .done() or .next() will have the json object which got returned by the server. However when I call getTokenExpiryAsync() something else happens.

getTokenExpiryAsync = function () {
    if (token === null) {
        return new Date();
    }

    token.then(
        function complete(result){
            console.log(result);
        },
        function onerror(error) {
            console.log(error);
        },
        function onprogress(data) {
        });
}

instead it doesn't seem to call any of the functions in the .then() it just skips right past it!. Strict mode is enabled so my token variable does have a promise inside of it. Otherwise it would of errored as it wouldn't be able to find the .done() method?

my question is why is this happerning and how can I get the expected behaviour that I want (token having a promise stored in it from getAccessTokenAsync which I can access in other methods).

In your code, it's unnecessary to create a new WinJS.Promise because WinJS.xhr().then will return the promise you want. To give the background, there are two ways to attach completed handlers to a promise: .then and .done. Both take the same arguments but differ in return value. .done returns undefined, as it's meant to be used at the very end of a promise chain.

.then, on the other hand, returns a promise that's fulfilled when the completed (or error handler) returns, and the fulfillment value is the value returned from the completed handler (or the error handler).

(By the way, I've written a bunch more on promises to clarify issues like this. A short version can be found All about promises (Windows Dev Blog); a more complete version can be found in Appendix A, "Demystifying Promises," of my free ebook, Programming Windows Store Apps in HTML, CSS, and JavaScript, Second Edition , which is in it's second preview right now.)

When writing any async function of your own, the best pattern to use when calling other existing async functions (like WinJS.xhr) is to return a promise from its .then. So in your case, you want getAccessTokenAsync to look like this:

getAccessTokenAsync = function (username, password) {
    var serializedData = {
        username: username, password: password,
    };

    return WinJS.xhr({
            type: "post",
            url: "http://127.0.0.1:8080/authenticate/login",
            responseType: "json",
            data: JSON.stringify(serializedData)
        }).then(
            function complete(result){
                return JSON.parse(result.responseText);
            }
        );
    })
}

This will return a promise, which you assign to token, whose fulfillment value will be the result from JSON.parse(result.responseText).

Let me explain now why your original use of new WinJS.Promise is incorrect--it's a common misunderstanding that my other writings clarity. The function argument that you give to the constructor here itself receives three arguments. Each argument is another function, each of which I call a "dispatcher," and you get one for complete, error, and progress. The body of your code inside the promise much call these dispatchers upon the appropriate events.

Those dispatchers, in turn, then call the completed, error, and progress handlers for any functions subscribed through the promise's .then or .done. Calling these dispatchers, in other words, is the only way that you actually triggers calls to those handlers.

Now in your original code you never actually call any of these. You've kept it simple by having the WinJS.Promise constructor just pay attention to the completed dispatcher. However, when your WinJS.xhr call completed, you're not calling this dispatcher. Part of the confusion is that you have an argument called complete, and then name your completed handler for WinJS.xhr().done "complete" as well. If you set breakpoints on the last JSON.parse call, it should be getting hit, but the value your returning just gets swallowed because it's never passed to the complete dispatcher .

To correct this, you'd want your original code to look like this:

return new WinJS.Promise(function (completeDispatch) {  //Name the dispatcher for clarity
    WinJS.xhr({
        type: "post",
        url: "http://127.0.0.1:8080/authenticate/login",
        responseType: "json",
        data: JSON.stringify(serializedData)
    }).done(
        function (result) {  //Keep this anonymous for clarity
            completeDispatch(JSON.parse(result.responseText));
        }
    );
})

This should work as well. However, it's still easiest to just return the promise from WinJS.xhr().then() as I originally noted, because you don't need another promise wrapper at all.

With either of these changes, you should now see a call to the completed handler within getTokenExpiryAsync.

Let's talk about other parts of your code now. First of all, token will always be set to a promise even if there's an error condition, so you'll never see a null case inside getTokenExpiryAsync. Secondly, if you use the new WinJS.Promise code as above, you'll never see error or progress cases, because you're never calling the errorDispatcher or progressDispatcher. This is another good reason to just use the return from WinJS.xhr().then() instead.

So you'll need to think through your error handling a little more closely here. What, exactly, are the cases where you want to call new Date() for an expiry? Do you do this when the xhr call fails, or when the response from a successful call returns empty?

One way to handle errors is to use the new WinJS.Promise variant above, with WinJS.xhr().done(), where you subscribe an error handler to .done. In that error handler you then determine whether you want to propagate the error, or whether you want to still fulfill the wrapper promise with a new Date, by calling completeDispather(new Date());. For other errors, you'd call the errorDispatcher. (Note that all this assumes that a successful xhr response contains the same format of data as new Date(), otherwise you're intermixing data values and would want to parse the date out of the response instead of just returning the whole response.)

return new WinJS.Promise(function (completeDispatch) {  //Name the dispatcher for clarity
    WinJS.xhr({
        type: "post",
        url: "http://127.0.0.1:8080/authenticate/login",
        responseType: "json",
        data: JSON.stringify(serializedData)
    }).done(
        function (result) {  //Keep this anonymous for clarity
            completeDispatch(JSON.parse(result.responseText));
        },
        function (e) {
            completeDispatch(new Date());  //Turns an xhr error into success with a default.
        }
    );
})

What I've just described is really a good way to catch errors in your core operation and then inject a default value, which is what I believe you intend.

If you use the return value from WinJS.xhr().then(), on the other hand (the first code variant), then you need to put more of this logic inside getTokenExpiryAsync. (By the way, this code, as you show it, is synchronous, and one code path returns a new Date and the other return undefined, so it's not quite what you want.)

Now because token itself is a promise, this getTokenExpiryAsync does need to be async itself, and therefore needs to also return a promise for the expiry. Here's how you'd write that:

function getTokenExpiryAsync (token) { //I'd pass token as an argument here
    return token.then(
        function complete(result) {
            return result; //Or parse the date from the original response.
        },
        function error(e) {
            return new Date(); 
        }
    );
}

And then in your calling code you'll need to say:

getTokenExpiryAsync(token).then(function (expiry) {
    lastTokenTime = expiry;
}

Again we're making use of the return value of then being another promise whose fulfillment value is what's returned from the completed or error methods. If token is in an error state (WinJS.xhr failed), then your call to .then will invoke the error handler, where you then return the desired default. Otherwise you return whatever expiry you want from the response. Either way, you get a date from this promise from .then in the original calling code.

I know this can be a bit confusing, but it's the nature of the Promises/A specification and async coding and not WinJS in particular.

Hope all this is worth your bounty. :)

It looks like your then functions aren't called because you aren't calling your promise function's complete callback. Also, your WinJS.xhr is a promise so you can just return that without wrapping it in another promise.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM