异步请求的设计模式,以处理成功,失败,重试? (JavaScript的)

时间:2013-09-10 16:08:14

标签: javascript ajax titanium-mobile

我正在编写一个Appcelerator Titanium的移动应用程序,它可以提供很多不同的xhr请求。这不是Appcelerator Titanium特有的问题。但如果你写了一些代码,我希望它是javascript。

应用程序需要进行身份验证,必须记录用户进行某些交互等等。

我已经到了任何请求可能得到任何响应的地步,例如:

  • 未经过身份验证
  • 未记录
  • bad params
  • 成功
  • ...

请求包含在不同的模型方法或帮助程序中。

问题是,我对这种应用并不熟悉。我想知道最佳做法是什么。

例如,一些真正的问题是:

  • 如果应用未经过身份验证(令牌已过期,首次启动),应用是否应尝试对自身进行身份验证,然后再次发送被拒绝的请求? (对用户透明)

  • 我应该在每次应用启动时发送身份验证请求,然后“忘记”它吗?

我面临的问题是,如果我尝试为每个请求处理此问题,代码会变得很快。充满嵌套的回调,重试条件,各种事件监听器来管理等等。它只是感觉不是很“好”。并且它根本不是DRY,当我真正需要的是任何请求,检查出错了,尝试修复它(如果不是则进行身份验证,如果可能则自动登录或显示登录UI等等)然后如果可行则重试原始请求几次,如果需要就中止。

我一直在关注承诺模式,但只知道理论,不知道它是否是我需要的。

所以我欢迎任何关于这个特殊问题的建议。我想知道像“Facebook”这样的应用程序如何处理这个问题。

感谢您的帮助

1 个答案:

答案 0 :(得分:1)

这个问题不容易回答,但让我试着给你一些想法:

最重要的是,在对应用程序中的任何内容进行编码之前,就是API本身。它必须可靠并符合标准。我不会在这里详细介绍,但编写良好的RESTful API可以显着降低httpClient的复杂性。它必须使用标准的http状态代码以及POST,GET,PUT,DELETE等方法进行响应。

George Reese写的The REST API Design Handbook非常好。

我对使用Titanium的httpClients的方法是一个单独的模块,在需要的地方通过require()加载。我一次只关注一个客户端,因为我遇到了多个并行调用的大量问题。无论何时进行呼叫,客户端都会检查是否已有正在进行的呼叫,并在必要时将其发送到队列。

让我举个例子。为简洁起见,我遗漏了很多东西:

// lib/customClient.js
var xhrRequest;     // This will be our HTTPClient
var callQueue = []; // This will be our queue

// Register the request
// params are:
// method (e.g. 'GET')
// url (e.g. 'http://test.com/api/v1/user/1')
// done (callback function)
function registerRequest(params) {
    if(!xhrRequest) {
        sendRequest(params);
    } else {
        queueRequest(params);
    }
}

// This simply sends the request
// to the callQueue
function queueRequest(params) {
    callQueue.push(params);
}

// Send the request with the params from register
// Please note that I do not hardcode error messages,
// I just do it here so it is easier to read
function sendRequest(params) {

    // Set callback if available and valid
    var callback = params.done && typeof(params.done) === "function" ? params.callback : null;

    // Set method
    var method = params.method || 'GET';

    // Create the HTTP Client
    xhrRequest = Ti.Network.createHTTPClient({
        // Success
        onload: function() {
            // You can check for status codes in detail here
            // For brevity, I will just check if it is valid
            if (this.status >= 200 && this.status < 300) {
                if(this.responseText) {
                    // You might want to check if it can be parsed as JSON here
                    try {
                        var jsonData = JSON.parse(this.responseText);
                        if(callback) callback({ success: true, response: jsonData });
                    } catch(e) {
                        if(callback) callback({ success: false, errormessage: 'Could not parse JSON data' });
                    }
                    processQueue();
                } else {
                    if(callback) callback({ success: false, errormessage: 'No valid response received' });
                    processQueue();
                }
            } else {
                if(callback) callback({ success: false, errormessage: 'Call response is success but status is ' + this.status });
                processQueue();
            }
        },
        // Error
        onerror: function(e) {
            if(this.responseText) {
                try {
                    var jsonData = JSON.parse(this.responseText);
                    if(callback) callback({ success: false, reponse: jsonData });
                } catch(e) {};
            }
            processQueue();
        },
    });

    // Prepare and send request
    // A lot more can (and should) be configured here, check documentation!
    xhrRequest.setTimeout(10000);
    xhrRequest.open(method, params.url);
    xhrRequest.send();
}

// Checks if there is anything else in the queue
// and sends it
function processQueue() {
    xhrRequest = null;
    var nextInQueue = callQueue.shift();
    if(nextInQueue) sendRequest(nextInQueue);
}

// Our public API
var publicAPI = {
    sendRequest: function(params) {
        registerRequest(params);
    }
};

module.exports = publicAPI;

然后我可以从任何其他控制器/视图发送呼叫

var customClient = require('lib/customClient'); // omit 'lib' if you use alloy

// Send the request
customClient.sendRequest({
        method        : 'GET',
        url           : 'http://test.com/api/v1/user/1',
        done          : function(response) {
                           Ti.API.debug(JSON.stringify(response));
                        }
});

请注意,这不是完整的,不检查连接性,没有真正的错误处理等,但它可能有助于您了解。

我认为这里有很多东西需要讨论,但我现在暂时停在这里......