前后分离模型之封装 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 异步处理相关的内容可以参考
- 从小小题目逐步走进 JavaScript 异步调用
- 闲谈异步调用“扁平”化
- 从地狱到天堂,Node 回调向 async/await 转变
- 理解 JavaScript 的 async/await
- 从不用 try-catch 实现的 async/await 语法说错误处理
自己封装工具函数
在处理 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 总让人感觉如芒在背,想把它去掉。有两个办法
- 修改所有业务中的调用,去掉
.done()
、.fail()
和.always()
,改成.then()
。这一步工作量较大,但基本无痛,因为 jQuery Promise 本身支持.then()
。但是有一点需要特别注意,这一点稍后说明 - 自己写个适配器,兼容 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 调用为例,看似在讲述异步调用。但实际想告诉大家的东西是:如何将一个常用的功能封装起来,实现代码重用和更简洁的调用;以及在封装的过程中需要考虑的问题——向前和向后的兼容性,在做工具函数封装的时候,应该尽量避免和某个特定的工具特性绑定,向公共标准靠拢——不知大家是否有所体会。

低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
记录徒手解决cranberry病毒的过程
起始 今天中午测试反馈说线上系统频繁的报502错误,并且响应极慢。开始怀疑是公司哪位小哥在下载小电影,但打开其他网站都很快。于是继续怀疑难道是业务激增导致带宽被占满了,于是登录监控界面,显示只用了80Mb,带宽也没占满。 发现根本原因 ssh上服务器之后,本能的执行top命令,返现cranberry进程几乎把cpu吃满了。 于是尝试kill掉进程 kill -9 14465 kill掉之后,cranberry又会立即自动重启。在紧盯屏幕之后,惊喜的发现crontab进程。于是大喜过望,这哥们会啊。crontab命令教程于是查看crontab的列表 crontab -l 看到如下命令,会通过定时任务去远程服务器下载脚本。 于是把crontab的列表删掉 crontab -r 然后,以为kill掉cranberry之后,就ok了,谁知道还是自动重启。那只能耐着性子,把脚本下载下来研究一下了,脚本内容如下: #!/bin/bash SHELL=/bin/sh PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin p...
- 下一篇
一个脚本引发的血案
我们本身是一家互联网金融公司,公司的主流业务就是p2p,因为各种原因吧,15年底启动建设众筹平台。考虑到前期开发过程中的一些弊端和架构经验,本次架构引用了dubbo做soa服务的治理,web容器nginx+tomcat,后端语言采用java,框架选择spring+mybaits,前端模板引擎使用的是btl,app采用原生+h5的模式。这个架构可以参考我之前写的文章从零到百亿互联网金融架构发展史中的第三代系统架构,之前的文章主要介绍了架构的变迁,本篇文章主要介绍在第三代平台中遇到的问题以及解决方法。 首先介绍一下众筹系统的部署架构(如下图),网站和app请求都是首先到最前端的nginx,如果只是静态内容的访问nginx直接处理后返回;动态请求分别转发到后端的tomcat前端服务层,前端服务层只关注页面端业务逻辑不涉及数据库的操作,如果只是页面框架渲染以及不涉及数据库的请求,在前端服务层直接处理返回;如果涉及到数据库操作或者核心业务逻辑,前端服务层通过dubbo调用后端的接入层服务或者核心层服务。 {:.center} 上线在生产测试期间,发现tomcat过一段时间就会莫名奇妙的down掉...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- Red5直播服务器,属于Java语言的直播服务器
- CentOS7安装Docker,走上虚拟化容器引擎之路
- Springboot2将连接池hikari替换为druid,体验最强大的数据库连接池
- Linux系统CentOS6、CentOS7手动修改IP地址
- CentOS7设置SWAP分区,小内存服务器的救世主
- SpringBoot2整合MyBatis,连接MySql数据库做增删改查操作
- Windows10,CentOS7,CentOS8安装Nodejs环境
- CentOS8,CentOS7,CentOS6编译安装Redis5.0.7
- SpringBoot2整合Thymeleaf,官方推荐html解决方案
- SpringBoot2编写第一个Controller,响应你的http请求并返回结果