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 调用为例,看似在讲述异步调用。但实际想告诉大家的东西是:如何将一个常用的功能封装起来,实现代码重用和更简洁的调用;以及在封装的过程中需要考虑的问题——向前和向后的兼容性,在做工具函数封装的时候,应该尽量避免和某个特定的工具特性绑定,向公共标准靠拢——不知大家是否有所体会。

前后分离模型之封装 Api 调用的更多相关文章

  1. 依图语音API的C#封装以及调用进行语音转写的处理

    对于语音识别,一般有实时语音识别和语音文件的识别处理等方式,如在会议.培训等场景中,可以对录制的文件进行文字的转录,对于转录文字的成功率来说,如果能够转换90%以上的正确语音内容,肯定能减轻很多相关语 ...

  2. 在eclipse中API的封装和调用

    自己写的API的封装和调用:1.写好api的方法的实现类.2.抽取一个javadoc文档.file->Export->java->javadoc->finish->Yes ...

  3. ABP开发框架前后端开发系列---(4)Web API调用类的封装和使用

    在前面随笔介绍ABP应用框架的项目组织情况,以及项目中领域层各个类代码组织,以及简化了ABP框架的各个层的内容,使得我们项目结构更加清晰.上篇随笔已经介绍了字典模块中应用服务层接口的实现情况,并且通过 ...

  4. Vue.js——使用$.ajax和vue-resource实现OAuth的注册、登录、注销和API调用

    概述 上一篇我们介绍了如何使用vue resource处理HTTP请求,结合服务端的REST API,就能够很容易地构建一个增删查改应用.这个应用始终遗留了一个问题,Web App在访问REST AP ...

  5. 信鸽推送 .NET (C#) 服务端 SDK rest api 调用库(v1.2)

    信鸽推送 .NET  服务端 SDK rest api 调用库-介绍 该版本是基于信鸽推送v2版本的时候封装的,先拿出来与大家分享,封装还还凑合,不依赖其他http调用件,唯一依赖json序列化dll ...

  6. (36)老版和新版API调用

    ---------更新时间18:06 2016-09-18 星期日------- *前言   我用的是odoo8,但里面有相当多的api是以前版本,这时若我们自己开发的   插件采用新版本api,里面 ...

  7. 阿里云 API调用实践(python语言)

    1.结论:阿里云的SDK开发,其实就是远程调用API,python的代码就是一个外壳,核心是封装成一个http报文,利用json格式,进行RPC调用. 2.SDK调用API的套路如下: # -*- c ...

  8. 如何用Baas快速在腾讯云上开发小程序-系列3 :实现腾讯云COS API调用

    版权声明:本文由贺嘉 原创文章,转载请注明出处: 文章原文链接:https://www.qcloud.com/community/article/640268001487425627 来源:腾云阁 h ...

  9. Vue.js——使用$.ajax和vue-resource实现OAuth的注册、登录、注销和API调用

    转自:https://www.cnblogs.com/keepfool/p/5665953.html 概述 上一篇我们介绍了如何使用vue resource处理HTTP请求,结合服务端的REST AP ...

随机推荐

  1. PKUWC2019游记&&WC2019游记

    今天好颓,不想写代码了,写写游记 PKUWC2019游记&&WC2019游记 PKUWC2019游记 提前两天就来了中山纪中,考了两天模拟,第一天比较正常,但是可做题只有T3,第二天非 ...

  2. user-modify属性。

    user-modify属性,用来控制用户能否对页面文本进行编辑.与标签的contentEditable属性类似.· -webkit-user-modify: read-only | read-writ ...

  3. Python 中常见错误总结

    IndentationError: unexpected indent Python 中强制缩进,, IndentationError: unexpected indent 缩进错误 这类错误非常常见 ...

  4. Python 爬取生成中文词云以爬取知乎用户属性为例

    代码如下: # -*- coding:utf-8 -*- import requests import pandas as pd import time import matplotlib.pyplo ...

  5. 素数筛选-hdu2710

    题目描述: 题目大意:找出具有最大素数因子的整数.如果有不止一个,则输出在输入文件中出现最早的一个. 解题思路:刚开始时,p数组中的元素全为0,刚开始对于素数 i,p[i]=0,用一个for循环,将是 ...

  6. 二分搜索-poj2785

    题目链接:http://poj.org/problem?id=2785 题目大意:要求输入A,B,C,D四个数组,从每个数组中分别取出一个数来相加,求出相加后 和为0 总共有多少种加法. #inclu ...

  7. IdentityServer4-参考

    一.Identity Resource 二.API Resource 三.Client 四.GrantValidationResult 五.Profile Service 六.IdentityServ ...

  8. element对象

    一.offset.client.scroll clientWidth,clientHeight,clientLeft,clientTop: offsetWidth,offsetHeight,offse ...

  9. android studio java工程 报错

    作者:韩梦飞沙 Author:han_meng_fei_sha 邮箱:313134555@qq.com E-mail: 313134555 @qq.com  android studio java工程 ...

  10. C# 使用PrintDocument 绘制表格 完成 打印预览 DataTable

    经过不断的Google与baidu,最终整理出来的打印类 主要是根据两个参考的类组合而成,稍微修改了一下,参考代码及来源见最后(其中一份是VB语言的) 其中遇到的一些问题也已经得到了解决(分页,打印预 ...