3

我有一个看起来像这样的功能

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

此函数将调用 getAccessTokenAsync,它将使用 xhr 向我的 Web 服务器发出请求。这看起来像这样:

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

我希望令牌现在可以在其中存储一个承诺。当我们然后调用.done().next()将拥有由服务器返回的 json 对象时。但是,当我打电话时getTokenExpiryAsync()会发生其他事情。

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

相反,它似乎没有调用 .then() 中的任何函数,它只是跳过它!启用了严格模式,因此我的令牌变量内部确实有一个承诺。否则它会出错,因为它无法找到 .done() 方法?

我的问题是为什么会发生这种情况以及如何获得我想要的预期行为(令牌中存储了来自 getAccessTokenAsync 的承诺,我可以通过其他方法访问)。

4

2 回答 2

5

在您的代码中,没有必要创建一个新的 WinJS.Promise 因为 WinJS.xhr().then 将返回您想要的承诺。作为背景,有两种方法可以将完成的处理程序附加到 Promise:.then 和 .done。两者都采用相同的参数,但返回值不同。.done 返回 undefined,因为它应该在 Promise 链的最末端使用。

另一方面,.then 返回一个在已完成(或错误处理程序)返回时履行的承诺,履行值是从已完成处理程序(或错误处理程序)返回的值。

(顺便说一句,我已经写了很多关于 Promise 的文章来澄清这样的问题。可以在All about Promises (Windows Dev Blog) 中找到一个简短的版本;在附录 A,“Demystifying Promises, “我的免费电子书,用 HTML、CSS 和 JavaScript 编写 Windows 商店应用程序,第二版,现在是它的第二个预览版。)

在编写您自己的任何异步函数时,调用其他现有异步函数(如 WinJS.xhr)时使用的最佳模式是从其 .then 返回一个 Promise。因此,在您的情况下,您希望 getAccessTokenAsync 看起来像这样:

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

这将返回一个您分配给令牌的承诺,其履行值将是 JSON.parse(result.responseText) 的结果。

现在让我解释一下为什么您最初使用 new WinJS.Promise 是不正确的——这是一个常见的误解,我的其他著作很清楚。您在此处提供给构造函数的函数参数本身接收三个参数。每个参数都是另一个函数,我将每个参数称为“调度程序”,您会得到一个用于完成、错误和进度的函数。Promise 中的代码主体会根据适当的事件调用这些调度程序。

反过来,这些调度程序为通过 promise 的 .then 或 .done 订阅的任何函数调用已完成、错误和进度处理程序。换句话说,调用这些调度程序是您实际触发对这些处理程序的调用的唯一方法。

现在,在您的原始代码中,您从未真正调用过这些代码中的任何一个。通过让 WinJS.Promise 构造函数只关注已完成的调度程序,您可以保持简单。但是,当您的 WinJS.xhr 调用完成时,您不会调用此调度程序。部分混淆是您有一个名为 complete 的参数,然后将 WinJS.xhr().done 的已完成处理程序命名为“完成”。如果您在最后一次 JSON.parse 调用上设置断点,它应该会被命中,但是您返回的值会被吞没,因为它从未传递给完整的dispatcher

要更正此问题,您希望原始代码如下所示:

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

这也应该有效。但是,正如我最初指出的那样,从 WinJS.xhr().then() 返回承诺仍然是最简单的,因为您根本不需要另一个承诺包装器。

通过这些更改中的任何一个,您现在应该会在 getTokenExpiryAsync 中看到对已完成处理程序的调用。

现在让我们谈谈代码的其他部分。首先,即使出现错误条件,token 也将始终设置为 promise,因此您永远不会在 getTokenExpiryAsync 中看到 null 情况。其次,如果您使用上述新的 WinJS.Promise 代码,您将永远不会看到错误或进度情况,因为您永远不会调用 errorDispatcher 或 progressDispatcher。这是只使用 WinJS.xhr().then() 的返回值的另一个很好的理由。

所以你需要在这里更仔细地考虑你的错误处理。究竟是什么情况下你想调用 new Date() 来过期?当 xhr 调用失败或成功调用的响应返回空时,你会这样做吗?

处理错误的一种方法是使用上面的新 WinJS.Promise 变体和 WinJS.xhr().done(),您可以在其中为 .done 订阅错误处理程序。然后,在该错误处理程序中,您可以通过调用 completeDispather(new Date()); 来确定是否要传播错误,或者是否仍要使用新日期来履行包装承诺。对于其他错误,您将调用 errorDispatcher。(请注意,所有这一切都假设成功的 xhr 响应包含与 new Date() 相同格式的数据,否则您将混合数据值并且希望从响应中解析日期,而不是仅返回整个响应。)

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

我刚刚描述的确实是一种在您的核心操作中捕获错误然后注入默认值的好方法,这就是我相信您想要的。

另一方面,如果你使用 WinJS.xhr().then() 的返回值(第一个代码变体),那么你需要在 getTokenExpiryAsync 中放入更多的这种逻辑。(顺便说一下,正如你所展示的,这段代码是同步的,一个代码路径返回一个新的日期,另一个返回未定义,所以它不是你想要的。)

现在因为令牌本身是一个承诺,所以这个 getTokenExpiryAsync 本身确实需要是异步的,因此需要返回一个到期的承诺。以下是你的写法:

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

然后在您的调用代码中,您需要说:

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

我们再次使用 then 作为另一个 promise 的返回值,它的实现值是从 completed 或 error 方法返回的值。如果令牌处于错误状态(WinJS.xhr 失败),那么您对 ​​.then 的调用将调用错误处理程序,然后您将在其中返回所需的默认值。否则,您会从响应中返回您想要的任何到期时间。无论哪种方式,您都可以从原始调用代码中的 .then 中获得此承诺的日期。

我知道这可能有点令人困惑,但这是 Promises/A 规范和异步编码的本质,而不是特别是 WinJS。

希望这一切都值得你的赏金。:)

于 2013-11-04T17:22:35.313 回答
2

看起来你的 then 函数没有被调用,因为你没有调用你的 promise 函数的完整回调。此外,您的 WinJS.xhr 是一个承诺,因此您可以直接返回它而无需将其包装在另一个承诺中。

于 2013-11-02T03:35:07.813 回答