如何包装自定义 json:api 客户端并使调用流畅且异步(非 ES6)

How to wrap a custom json:api client and make calls fluent and async (Non-ES6)

我想为 restful API 创建一个遵循 json:api 规范的自定义客户端。因此,我创建了这个具有异步支持的简单客户端:

MyTools.Api = (function () {
    "use strict";
    //#region Private
    function ajax(url, method, data) {
        return new Promise(function (resolve, reject) {
            let request = new XMLHttpRequest();
            request.responseType = 'json';
            //Open first, before setting the request headers.
            request.open(method, url, true);
            request.setRequestHeader('Authorization', 'Bearer ' + window.headerToken)
            request.onreadystatechange = function () {
                if (request.readyState === XMLHttpRequest.DONE) {
                    if (request.status === 200) {
                        resolve(request.response);
                    } else {
                        reject(Error(request.status));
                    }
                }
            };
            request.onerror = function () {
                reject(Error("Network Error"));
            };
            request.open(method, url, true);
            request.send(data);
        });
    }

    //#endregion
    //#region Public
    return function (url, method, data) {
        url = window.apiBasePath + url;
        return ajax(url, method, data);
    };
    //#endregion
})(MyTools.Api || {});

我正在将令牌从后端 (.net) 传递到一个名为 headerToken 的 window 全局变量。与基本路径 (apiBasePath) 相同。 现在,我可以像这样称呼这个客户

CTools.Api("/dashboard/users/", "GET").then(function (result) {
console.log(result);
});

我的目标是创建一种更流畅的方式来使用 api.For 示例,我想调用如下内容:

mytools.api.dashboard.users.get().then(function (result) {
    console.log(result);
    });

mytools.api.dashboard.users.get(fliteroptions).then(function (result) {
    console.log(result);
    });

并且如果有另一个模块可以使用 like

mytools.api.basket.items.get(fliteroptions).then(function (result) {
    onsole.log(result);
    });

仪表板和购物篮将有不同的 url。客户端和 url 建筑都将在 mytools 命名空间内创建。此外,变量 headerToken 和 apiBasePath 将在 rest 调用后分配到 mytools 构造函数中。

在这种情况下使用什么设计模式? 请记住,我想要一个非 ES6 解决方案。

基本上只有两种方法可以做您想做的事;每条路线(dashboardbasket 等)都是具有 fetch 行为等​​的完整 api 对象,或者 dashboard 中的 get() 方法等映射回 api.

像这样:

class Route {
    constructor(url, api) {
        this.url = url;
        this.api = api;
    }
    
    get(filterOptions) {
        return this.api.get(this.url, filterOptions)
    }
    
    post(filterOptions) {
        return this.api.post(this.url, filterOptions);
    }
}

class Api {
    constructor(basePath) {
        this.basePath = basePath;
    }
    
    get(route, filterOptions) {
        // filterOptions can be null here
        return fetch(`${this.basePath}/${route}`)
            .then(data => data.json())
    }
    
    post(route, filterOptions) {
        // filterOptions can be null here
        ...
    }
    
    ...
}

mytools.api = new Api();
mytools.api.dashboard = new Route('dashboard', mytools.api);
mytools.api.basket = new Route('basket', mytools.api);
...

你的 Route class 是一个简单的 class ,它包含一个 url 和其他任何东西,以及 api 实际上会做正在获取。

Api 包装你的 fetch 并处理任何 filterOptions 通过。

当您创建 Api 时,您也创建了所有路线。

缺点是您需要事先了解所有路线。

要更动态地做到这一点,您可以查看 Proxyhttps://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy

在这里,您可以实现一个每次访问您的对象时都会调用的函数:

var handler = {
    get: function(target, name) {
        return "Hello, " + name;
    }
};
var proxy = new Proxy({}, handler);

console.log(proxy.world); // output: Hello, world

您应该能够连接到它,以便 get 方法 return 成为 Api 对象,然后您可以直接调用它

为 ES5 编辑

自从我编写纯 ES5 以来已经有一段时间了,但是转换 Route class,例如,应该看起来像这样:

function Route(url, api) {
    this.url = url;
    this.api = api;
}

Route.prototype.get = function(filterOptions) {
    return this.api.get(this.url, filterOptions)
}

Route.prototype.post = function(filterOptions) {
    return this.api.post(this.url, filterOptions)
}

创建对象应该保持不变 - new Route(url, api)

我不确定 fetch 是否是 ES5 - 您需要查看代码的位置 运行 看看它是否受支持。同上承诺;您要么需要使用像 Bluebird 这样的库,要么使用回调。

有关 classes 的更多信息,请点击此处:https://medium.com/@apalshah/javascript-class-difference-between-es5-and-es6-classes-a37b6c90c7f8

编辑动态 URL

如果我没听错的话,您是从 REST 调用中获取 URL。

假设此调用的 return 类似于:

{
    "dashboard": {
        "url": "dashboard/",
        "token": "..."
    },
    "basket": {
        "url": "basket/",
        "token": "..."
    },
    ...
}

此时,您现在可以创建您的 api:

mytools.api = new Api();
mytools.api.dashboard = new Route(jsonObj.dashboard.url, mytools.api);
mytools.api.basket = new Route(jsonObj.basket.url, mytools.api);
...

您还可以传入 token 或 REST JSON 对象中的任何其他内容。不过,它确实意味着一些事情:

  1. 在进行初始 REST 调用之前,您无法访问 mytools.api.dashboard 等 - 它不会指向任何内容
  2. 您需要事先了解您的路线 - 例如在这里,我们知道我们在 mytools.api.dashboard 变量
  3. 后面有一条路线

关于第 2 点,您不能真正拥有真正的动态 url(即您可以将 "profile" 添加到 REST JSON 而无需添加相应的 mytools.api.profile 调用在你的代码库中)而不用重新考虑你如何处理事情。您在这里所做的(使 API 更易于使用)是预编译的 - 就像您在编写代码时一样。但是,直到运行时才知道您的路线