|
前后分离模型之封装 Api 调用 |
|
|
前后分离模型之封装Api调用Ajax和异步处理调用API访问数据采用的Ajax方式,这是一个异步过程,异步过程最基本的处理方式 是事件或回调,其实这两种处理方式实现原理差不多,都需要在调用异步过程的时候传入一个在异步过程结束的时候调用的接口。比如jQuer yAjax的?success?就是典型的回调参数。不过使用jQuery处理异步推荐使用Promise处理方式。Prom ise处理方式也是通过注册回调函数来完成的。jQuery的Promise和ES6的标准Promise有点不一样,但 在?then?上可以兼容,通常称为thenable。jQuery的Promise没有提供?.catch()?接口,但它自己 定义的?.done()、.fail()?和?.always()?三个注册回调的方式也很有特色,用起来很方便,它是在事件的方式来注册 的(即,可以注册多个同类型的处理函数,在该触发的时候都会触发)。当然更直观的一点的处理方式是使用ES2017带来的async /await方式,可以用同步代码的形式来写异步代码,当然也有一些坑在里面。对于前端工程师来说,最大的坑就是有些浏览器不支持,需要 进行转译,所以如果前端代码没有构建过程,一般还是就用ES5的语法兼容性好一些(jQuery的Promise是支持ES5 的,但是标准Promise要ES6以后才可以使用)。自己封装工具函数在处理Ajax的过程中,虽然有现成的库(比如j Query.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",pa ssword:"passwd"}}).done(function(data){if(data.code){ale rt(data.message||"登录失败!");}else{window.location.assign("hom e");}}).fail(function(){alert("服务器错误");});初步封装同一项目中,这样的Aja x调用,基本上只有?data?部分和?.done?回调中的?else?部分不同,所以进行一次封装会大大减少代码量,可以这样封装f unctionappAjax(action,params){vardeffered=$.Deferred();jQ uery.ajax(apiUrl,{type:"post",dataType:"json",data:$.exte nd({action:action},params)}).done(function(data){//当cod e为0或省略时,表示没有错误,//其它值表示错误代码if(data.code){if(data.message ){//如果服务器返回了消息,那么向用户呈现消息//resolve(null),表示不需要后续进行业务处理alert( data.message);deffered.resolve();}else{//如果服务器没返回消息,那么把dat a丢给外面的业务处理deferred.reject(data);}}else{//正常返回数据的情况deffer ed.resolve(data);}}).fail(function(){//Ajax调用失败,向用户呈现消息,同时 不需要进行后续的业务处理http://www.gw638.cnalert("服务器错误");deffered.resolve ();});returndeferred.promise();}而业务层的调用就很简单了appAjax("login",{ username:"uname",password:"passwd"}).done(function(data){if (data){window.location.assign("home");}}).fail(function(){a lert("登录失败");});更换API调用接口上面的封装对调用接口和返回数据进行了统一处理,把大部分项目接口约定的内容都处 理掉了,剩下在每次调用时需要处理的就是纯粹的业务。现在项目组决定不用jQuery的Ajax,而是采用axios来调用A PI(axios不见得就比jQuery好,这里只是举例),那么只需要修改一下?appAjax()?的实现即可。所有业务调用都 不需要修改。假设现在的目标环境仍然是ES5,那么需要第三方Promise提供,这里拟用Bluebird,兼容原生Prom ise接口(在HTML中引入,未直接出现在JS代码中)。functionappAjax(action,params) {vardeffered=$.Deferred();axios.post(apiUrl,{data:$.ext end({action:action},params)}).then(function(data){...}, function(){...});returndeferred.promise();}这次的封装采用了axios来实 现WebApi调用。但是为了保持原来的接口(jQueryPromise对象有提供?.done()、.fail()?和?. always()?事件处理),appAjax?仍然不得不返回jQueryPromise。这样,即使所有地方都不再需要使用jQ uery,这里仍然得用。去除jQuery就只在这里使用jQuery总让人感觉如芒在背,想把它去掉。有两个办法修改所有业务中的 调用,去掉?.done()、.fail()?和?.always(),改成?.then()。这一步工作量较大,但基本无痛,因为jQ ueryPromise本身支持?.then()。但是有一点需要特别注意,这一点稍后说明自己写个适配器,兼容jQueryPr omise的接口,工作量也不小,但关键是要充分测试,避免差错。上面提到第1种方法中有一点需要特别注意,那就是?.then() ?和?.done()?系列函数在处理方式上有所不同。.then()?是按Promise的特性设计的,它返回的是另一个Prom ise对象;而?.done()?系列函数是按事件机制实现的,返回的是原来的Promise对象。所以像下面这样的代码在修改时就 要注意了appAjax(url,params).done(function(data){console.log("第1 处处理",data)}).done(function(data){console.log("第2处处理",data )});//第1处处理{}//第2处处理{}http://www.44226.net简单的把?.done()? 改成?.then()?之后(注意不需要使用Bluebird,因为jQueryPromise支持?.then())appAj ax(url,params).then(function(data){console.log("第1处处理",dat a);}).then(function(data){console.log("第2处处理",data);});// 第1处处理{}//第2处处理undefined原因上面已经讲了,这里正确的处理方式是合并多个done的代码,或 者在?.then()?处理函数中返回?data:appAjax(url,params).then(function(data) {console.log("第1处处理",data);returndata;}).then(function(d ata){console.log("第2处处理",data);});使用Promise接口改善设计我们的?appA jax()?接口部分也可以设计成Promise实现,这是一个更通用的接口。既使用不用ES2015+特性,也可以使用像jQ ueryPromise或Bluebird这样的三方库提供的Promise。functionappAjax(action ,params){//axios依赖于Promise,ES5中可以使用Bluebird提供的Promiser eturnaxios.post(apiUrl,{data:$.extend({action:action},pa rams)}).then(function(data){//这里调整了判断顺序,会让代码看起来更简洁if(!data .code){returndata;}if(!data.message){throwdata;}alert( data.message);},function(){alert("服务器错误");});}不过现在前端有构建工具,可以 使用ES2015+配置Babel,也可以使用TypeScript……总之,选择很多,写起来也很方便。那么在设计的时候就 不用局限于ES5所支持的内容了。所以可以考虑用Promise+async/await来实现asyncfunction appAjax(action,params){//axios依赖于Promise,ES5中可以使用Bluebir d提供的Promiseconstdata=awaitaxios.post(apiUrl,{data:$.ex tend({action:action},params)})//这里模拟一个包含错误消息的结果,以便后面统一处理错误 //这样就不需要用try...catch了http://f-1.cc.catch(()=>({code:- 1,message:"服务器错误"}));if(!data.code){returndata;}if(!da ta.message){throwdata;}alert(data.message);}上面代码中使用?.catch() ?来避免?try...catch...?的技巧在从不用try-catch实现的async/await语法说错误处理中 提到过。当然业务层调用也可以使用async/await(记得写在async函数中):constdata=awaita ppAjax("login",{username:"uname",password:"passwd"}).catch(( )=>{alert("登录失败");});if(data){window.location.assign("home");}对于多次?.done()?的改造:constdata=awaitappAjax(url,params);console.log("第1处处理",data);console.log("第2处处理",data);小结本文以封装Ajax调用为例,看似在讲述异步调用。但实际想告诉大家的东西是:如何将一个常用的功能封装起来,实现代码重用和更简洁的调用;以及在封装的过程中需要考虑的问题——向前和向后的兼容性,在做工具函数封装的时候,应该尽量避免和某个特定的工具特性绑定,向公共标准靠拢——不知大家是否有所体会。 |
|
|
|
|
|
|
|
|
|
|