前后分离模型之封装 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 总让人感觉如芒在背,想把它去掉。有两个办法
修改所有业务中的调用,去掉
.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 调用为例,看似在讲述异步调用。但实际想告诉大家的东西是:如何将一个常用的功能封装起来,实现代码重用和更简洁的调用;以及在封装的过程中需要考虑的问题——向前和向后的兼容性,在做工具函数封装的时候,应该尽量避免和某个特定的工具特性绑定,向公共标准靠拢——不知大家是否有所体会。
前后分离模型之封装 Api 调用的更多相关文章
- 依图语音API的C#封装以及调用进行语音转写的处理
对于语音识别,一般有实时语音识别和语音文件的识别处理等方式,如在会议.培训等场景中,可以对录制的文件进行文字的转录,对于转录文字的成功率来说,如果能够转换90%以上的正确语音内容,肯定能减轻很多相关语 ...
- 在eclipse中API的封装和调用
自己写的API的封装和调用:1.写好api的方法的实现类.2.抽取一个javadoc文档.file->Export->java->javadoc->finish->Yes ...
- ABP开发框架前后端开发系列---(4)Web API调用类的封装和使用
在前面随笔介绍ABP应用框架的项目组织情况,以及项目中领域层各个类代码组织,以及简化了ABP框架的各个层的内容,使得我们项目结构更加清晰.上篇随笔已经介绍了字典模块中应用服务层接口的实现情况,并且通过 ...
- Vue.js——使用$.ajax和vue-resource实现OAuth的注册、登录、注销和API调用
概述 上一篇我们介绍了如何使用vue resource处理HTTP请求,结合服务端的REST API,就能够很容易地构建一个增删查改应用.这个应用始终遗留了一个问题,Web App在访问REST AP ...
- 信鸽推送 .NET (C#) 服务端 SDK rest api 调用库(v1.2)
信鸽推送 .NET 服务端 SDK rest api 调用库-介绍 该版本是基于信鸽推送v2版本的时候封装的,先拿出来与大家分享,封装还还凑合,不依赖其他http调用件,唯一依赖json序列化dll ...
- (36)老版和新版API调用
---------更新时间18:06 2016-09-18 星期日------- *前言 我用的是odoo8,但里面有相当多的api是以前版本,这时若我们自己开发的 插件采用新版本api,里面 ...
- 阿里云 API调用实践(python语言)
1.结论:阿里云的SDK开发,其实就是远程调用API,python的代码就是一个外壳,核心是封装成一个http报文,利用json格式,进行RPC调用. 2.SDK调用API的套路如下: # -*- c ...
- 如何用Baas快速在腾讯云上开发小程序-系列3 :实现腾讯云COS API调用
版权声明:本文由贺嘉 原创文章,转载请注明出处: 文章原文链接:https://www.qcloud.com/community/article/640268001487425627 来源:腾云阁 h ...
- Vue.js——使用$.ajax和vue-resource实现OAuth的注册、登录、注销和API调用
转自:https://www.cnblogs.com/keepfool/p/5665953.html 概述 上一篇我们介绍了如何使用vue resource处理HTTP请求,结合服务端的REST AP ...
随机推荐
- oracle <> 选不出为null的部分
比如 tablea 的 字段b 为空,则 select * from tablea where b <> 'Y' 则查不出b is null 的部分
- #8 //HDU 5730 Shell Necklace(CDQ分治+FFT)
Description 给出长度分别为1~n的珠子,长度为i的珠子有a[i]种,每种珠子有无限个,问用这些珠子串成长度为n的链有多少种方案 题解: dp[i]表示组合成包含i个贝壳的项链的总方案数 转 ...
- python学习之基础语法
一.缩进 学习 Python 与其他语言最大的区别就是,Python 的代码块不使用大括号 {} 来控制类,函数以及其他逻辑判断.python 最具特色的就是用缩进来写模块. 缩进的空白数量是可变的, ...
- Python yaml模块
引用自:https://www.cnblogs.com/shaosks/p/7344771.html 一.简介 YAML 语言(发音 /ˈjæməl/ )的设计目标,就是方便人类读写.它实质上是一种通 ...
- BZOJ1179 [Apio2009]Atm Tarjan 强连通缩点 动态规划
欢迎访问~原文出处——博客园-zhouzhendong 去博客园看该题解 题目传送门 - BZOJ1179 题意概括 有一个有向图,每一个节点有一个权值,其中有一些结束点. 现在,你要从S出发,到达任 ...
- centos7.2下安装Mysql笔记
centos7.2下安装Mysql笔记 安装 MySQL 适用于 CentOS 7.0 或以后版本: yum install mariadb mariadb-server 适用于 CentOS 6.8 ...
- 005.Docker存储管理
一 Docker volume形态 因为Docker 采用 AFUS 分层文件系统时,文件系统的改动都是发生在最上面的容器层,在容器的生命周期内,它是持续的,包括容器在被停止后.但是,当容器被删除后, ...
- MySQL数据库crash的问题分析
[问题] 生产环境有多台slave服务器,不定期的会crash,下面是error log中的堆栈信息 Thread pointer: 0x7f1e54b26410 Attempting backtra ...
- 利用python计算多边形面积
最近业务上有一个需求,给出多边形面积. Google了一下,发现国内论坛给的算法都是你抄我我抄你,也不验证一下是否正确, 从 博客园到csdncsdn 然后传播到国内各个角落...真是无力吐槽了. 直 ...
- AGC01 A - BBQ Easy
目录 题目链接 题解 代码 题目链接 AGC01 A - BBQ Easy 题解 贪心 排序之后从大到小,没两组取小的那个 代码 #include<cstdio> #include< ...