前后分离模型之封装 Api 调用

Ajax 和异步处理

调用 API 访问数据运用的 Ajax
格局,那是一个异步过程,异步过程最要旨的处理模式是事件或回调,其实这二种处理形式实现原理差不多,都需要在调用异步过程的时候传出一个在异步过程截至的时候调用的接口。比如
jQuery Ajax 的 success 就是鹤立鸡群的回调参数。可是使用 jQuery
处理异步推荐使用 Promise 处理格局。

Promise 处理形式也是由此挂号回调函数来成功的。jQuery 的 Promise 和 ES6
的规范 Promise 有点不同等,但在 then 上得以兼容,通常称为
thenable。jQuery 的 Promise 没有提供 .catch() 接口,但它自己定义的
.done()、.fail() 和 .always()六个登记回调的艺术也很有特点,用起来很方便,它是在事件的情势来注册的(即,可以挂号多少个同品种的处理函数,在该触发的时候都会接触)。

本来更直观的一些的处理情势是运用 ES2017 带来的 async/await
情势,可以用协同代码的款型来写异步代码,当然也有部分坑在内部。对于前端工程师来说,最大的坑就是有些浏览器不扶助,需要展开转译,所以如果前端代码没有构建过程,一般仍然就用
ES5 的语法兼容性好一些(jQuery 的 Promise 是永葆 ES5 的,不过正式
Promise 要 ES6 未来才可以运用)。

关于 JavaScript 异步处理有关的情节可以参照

协调包裹工具函数

在处理 Ajax 的过程中,尽管有现成的库(比如 jQuery.ajax,axios
等),它说到底是为了通用目标设计的,在使用的时候依然免不了繁琐。而在项目中,对
Api
举行调用的长河几乎都差不多。倘诺计划适合,就连错误处理的措施都会是一律的。因而,在类型内的
Ajax
调用实际可以开展进一步的卷入,使之在品种内接纳起来更有利于。固然接口情势爆发变化,修改起来也更便于。

例如,当前接口要求拔取 POST 方法调用(暂不考虑 RESTful),参数必须概括
action,再次来到的多寡以 JSON
模式提供,假若出错,只要不是服务器卓殊都会回到特定的 JSON
数据,包括一个不对等 0 的 code 和可选的 message 属性。

这就是说用 jQuery 写这样一个 Ajax 调用,大概是这样

const apiUrl = "http://api.some.com/";

jQuery
    .ajax(url, {
        type: "post",
        dataType: "json",
        data: {
            action: "login",
            username: "uname",
            password: "passwd"
        }
    })
    .done(function(data) {
        if (data.code) {
            alert(data.message || "登录失败!");
        } else {
            window.location.assign("home");
        }
    })
    .fail(function() {
        alert("服务器错误");
    });

起来封装

一致系列中,这样的 Ajax 调用,基本上只有 data 部分和 .done 回调中的 else
部分不同,所以举行一回封装会大大裁减代码量,可以如此封装

function appAjax(action, params) {
    var deffered = $.Deferred();

    jQuery
        .ajax(apiUrl, {
            type: "post",
            dataType: "json",
            data: $.extend({
                action: action
            }, params)
        })
        .done(function(data) {
            // 当 code 为 0 或省略时,表示没有错误,
            // 其它值表示错误代码
            if (data.code) {
                if (data.message) {
                    // 如果服务器返回了消息,那么向用户呈现消息
                    // resolve(null),表示不需要后续进行业务处理
                    alert(data.message);
                    deffered.resolve();
                } else {
                    // 如果服务器没返回消息,那么把 data 丢给外面的业务处理
                    deferred.reject(data);
                }
            } else {
                // 正常返回数据的情况
                deffered.resolve(data);
            }
        })
        .fail(function() {
            // Ajax 调用失败,向用户呈现消息,同时不需要进行后续的业务处理
            alert("服务器错误");
            deffered.resolve();
        });

    return deferred.promise();
}

而业务层的调用就很简短了

appAjax("login", {
    username: "uname",
    password: "passwd"
}).done(function(data) {
    if (data) {
        window.location.assign("home");
    }
}).fail(function() {
    alert("登录失败");
});

更换 API 调用接口

下边的卷入对调用接口和重回数据开展了合并处理,把大部分品类接口约定的情节都处理掉了,剩下在历次调用时索要处理的就是纯粹的政工。

现今项目组决定决不 jQuery 的 Ajax,而是使用 axios 来调用 API(axios
不见得就比 jQuery 好,这里只是比喻),那么只需要修改一下 appAjax()的落实即可。所有事情调用都不需要修改。

若果现在的靶子环境仍旧是 ES5,那么需要第三方 Promise 提供,这里拟用
Bluebird,包容原生 Promise 接口(在 HTML 中引入,未直接出现在 JS
代码中)。

function appAjax(action, params) {
    var deffered = $.Deferred();

    axios
        .post(apiUrl, {
            data: $.extend({
                action: action
            }, params)
        })
        .then(function(data) { ... }, function() { ... });

    return deferred.promise();
}

本次的包装采纳了 axios 来贯彻 Web Api
调用。不过为了保持原来的接口(jQuery Promise 对象有提供 .done()、.fail()和 .always() 事件处理),appAjax 仍旧只好再次来到 jQuery
Promise。这样,即便拥有地点都不再需要使用 jQuery,这里依然得用。

类型中应有用仍旧不要 jQuery?请阅读怎么要用原生 JavaScript 代替
jQuery?

去除 jQuery

就只在此处运用 jQuery 总令人觉得如芒在背,想把它去掉。有六个章程

    1.修改所有业务中的调用,去掉 .done()、.fail() 和 .always(),改成
.then()。这一步工作量较大,但中央无痛,因为 jQuery Promise 本身襄助.then()。不过有某些亟待特别注意,这点稍后证实
    2.自己写个适配器,兼容 jQuery Promise
的接口,工作量也不小,但第一是要尽量测试,避免差错。
下面提到第 1 种办法中有一些内需特别注意,那就是 .then() 和 .done()连串函数在处理形式上有所不同。.then() 是按 Promise
的表征设计的,它回到的是另一个 Promise 对象;而 .done()系列函数是按事件机制落实的,重返的是原本的 Promise
对象。所以像下边这样的代码在改动时就要小心了

appAjax(url, params)
    .done(function(data) { console.log("第 1 处处理", data) })
    .done(function(data) { console.log("第 2 处处理", data) });
// 第 1 处处理 {}
// 第 2 处处理 {}

简易的把 .done() 改成 .then() 之后(注意不需要选择 Bluebird,因为 jQuery
Promise 辅助 .then())

appAjax(url, params)
    .then(function(data) { console.log("第 1 处处理", data); })
    .then(function(data) { console.log("第 2 处处理", data); });
// 第 1 处处理 {}
// 第 2 处处理 undefined

原因上边已经讲了,这军机章京确的处理形式是联合四个 done 的代码,或者在
.then() 处理函数中回到 data:

appAjax(url, params)
    .then(function(data) {
        console.log("第 1 处处理", data);
        return data;
    })
    .then(function(data) {
        console.log("第 2 处处理", data);
    });

运用 Promise 接口立异计划

咱俩的 appAjax() 接口部分也得以计划成 Promise
实现,这是一个更通用的接口。既使用不用 ES2015+ 特性,也可以利用像 jQuery
Promise 或 Bluebird 这样的三方库提供的 Promise。

function appAjax(action, params) {
    // axios 依赖于 Promise,ES5 中可以使用 Bluebird 提供的 Promise
    return axios
        .post(apiUrl, {
            data: $.extend({
                action: action
            }, params)
        })
        .then(function(data) {
            // 这里调整了判断顺序,会让代码看起来更简洁
            if (!data.code) { return data; }
            if (!data.message) { throw data; }
            alert(data.message);
        }, function() {
            alert("服务器错误");
        });
}

而是现在前端有构建工具,可以应用 ES2015+ 配置 Babel,也得以应用
TypeScript ……
不问可知,采用过多,写起来也很方便。那么在计划的时候就无须局限于 ES5
所匡助的始最终。所以可以考虑用 Promise + async/await 来落实

async function appAjax(action, params) {
    // axios 依赖于 Promise,ES5 中可以使用 Bluebird 提供的 Promise
    const data = await axios
        .post(apiUrl, {
            data: $.extend({
                action: action
            }, params)
        })
        // 这里模拟一个包含错误消息的结果,以便后面统一处理错误
        // 这样就不需要用 try ... catch 了
        .catch(() => ({ code: -1, message: "服务器错误" }));

    if (!data.code) { return data; }
    if (!data.message) { throw data; }

    alert(data.message);
}

地点代码中应用 .catch() 来制止 try … catch … 的技巧在从毫无
try-catch 实现的 async/await 语法说错误处理中提到过。

自然业务层调用也足以动用 async/await(记得写在 async 函数中):

const data = await appAjax("login", {
    username: "uname",
    password: "passwd"
}).catch(() => {
    alert("登录失败");
});

if (data) {
    window.location.assign("home");
}

对于频繁 .done() 的改造:

const data = await appAjax(url, params);
console.log("第 1 处处理", data);
console.log("第 2 处处理", data);

小结

正文以封装 Ajax
调用为例,看似在讲述异步调用。但骨子里想告知我们的东西是:怎么着将一个常用的遵守封装起来,实现代码重用和更简明的调用;以及在卷入的经过中需要考虑的问题——向前和向后的兼容性,在做工具函数封装的时候,应该尽量避免和某个特定的工具特性绑定,向国有规范靠拢——不知我们是不是具有体会。