这是一个对第三方js-sdk进行封装的小例子,希望整个思路过程对JS初学者有所启发。
1. 背景
某系统开发中引用第三方基于websocket的服务,需要对js-sdk进行封装,其最简单的demo如下:
// 触发event
window.doLogin()
// event的回调实现
window.cbLogin = res => {
if (res.code === 0) {
//doSomethingSuccess
}
else {
//doSomethingFail
}
}
先实现某个event的回调函数,然后触发某个event,第三方库会基于websocket发出请求,当有结果返回,会执行event的回调函数。
2. 最初实现
在业务代码 biz.js 中引入封装库 clink.js,然后注册回调函数,并且将业务的上下文传递回去:
//biz.js 业务代码
import clink from './clink'
class Action {
constructor () {
super()
// 注册回调函数
clink.cbLogin(this)
clink.cbLogout(this)
}
doLogin () {
window.doLogin()
}
// 在window.onload时候执行
componentDidMount() {
// 触发login事件
this.doLogin()
}
}
//clink.js 对sdk的封装
const clink = {}
clink.cbLogin = ctx => {
window.cbLogin = res => {
if (res.code === 0) {
//doSomethingSuccess
}
else {
//doSomethingFail
}
}
}
// 还存在其他event,代码结构类似,此处省略
clink.cbLogout = ...
clink.cbEvent1 = ...
clink.cbEvent2 = ...
biz.js是为了说明怎样来触发event的,我们主要来看clink.js:
// 实现了一个个event的回调
clink.cbLogin = ...
clink.cbLogout = ...
clink.cbEvent1 = ...
clink.cbEvent2 = ...
3. 需求改进
当由于网络不稳定等因素,使得回调函数得到的返回结果是失败,则需要重试,默认重试次数是3次。简单地说需要一个重试机制。
下面我们来实现这个重试,仅做到满足需求:
//clink.js
clink.cbLogin = ctx => {
const MaxRetryCount = 3
let retryCount = 0
window.cbLogin = res => {
if (res.code === 0) {
//doSomethingSuccess
}
else {
if (retryCount < MaxRetryCount) {
// retry
ctx.doLogin()
}
else {
//doSomethingFail
}
}
}
}
我们应该已经发现,每个回调函数中 MaxRetryCount
retryCount
if...else...
这些是重复的部分,既然是重复的部分,那我们应该做抽象和封装。
而显然,这里的 变量 是 doSomethingSuccess
和 doSomethingFail
,因为JavaScript中的函数是一等公民(First Class Object),可以把Function当成参数传递,所以我们把这两个doSomething
当成参数进行封装
4. 开始封装
4.1. 修改 if...else...语句
首先,两层 if...else...
语句不够优雅,做如下修改:
const success = parseInt(res.code, 10) === 0;
// 未成功且没达到重试上限,执行重试
if (!success && (retryCount < maxRetryCount)) {
// retry
}
// 未成功且达到重试上限,执行失败回调
else if (!success && (retryCount >= maxRetryCount)) {
// doSomethingFail
}
// 成功,执行成功回调
else {
// doSomethingSuccess
}
4.2. 定义高阶函数的参数
如上所述,将 doSomethingSuccess
和 doSomethingFail
作为两个参数。另外还需要一个参数对象来做一下设置,如重试次数等,具体如下:
/**
* 高阶辅助函数
* @param {Object} args - 参数对象
* @param {Object} args.ctx - Action上下文
* @param {string} args.name - callback名称
* @param {number} args.retry - 重试次数
* @param {Function} args.doEvent - 触发event
* @param {Function} successCallback - 成功回调函数
* @param {Function} failCallback - 失败回调函数
*
*/
const cHelper = (args, successCallback, failCallback) => {
}
3.3. 实现高阶函数
实现cHelper
,并且用cbLogin
、cbLogout
等来说明:
// clink.js
/**
* 高阶辅助函数
*/
const cHelper = (args, successCallback, failCallback) => {
let retryCount = 0
const maxRetryCount = args.retry || 3
return root[args.name] = res => {
const context = args.ctx
const success = parseInt(res.code, 10) === 0
if (!success && (retryCount < maxRetryCount)) {
args.doEvent && (args.doEvent instanceof Function) && args.doEvent()
retryCount++
}
else if (!success && (retryCount >= maxRetryCount)) {
failCallback(res)
}
else {
successCallback(res)
}
}
}
/**
* 登录回调函数
*/
clink.cbLogin = function (ctx) {
let me = this
let params = {
ctx: ctx,
name: 'cbLogin',
doEvent() {
me.doLogin()
}
}
cHelper(params, res => {
// doSomethingSuccess
}, res => {
// doSomethingFail
})
}
/**
* Event1回调函数(重试5次)
*/
clink.cbEvent1 = function (ctx) {
let me = this
let params = {
ctx: ctx,
retry: 5,
name: 'cbEvent1',
doEvent() {
me.doEvent1()
}
}
cHelper(params, res => {
// doSomethingSuccess
}, res => {
// doSomethingFail
})
}
/**
* Event2回调函数(不重试)
*/
clink.cbEvent2 = function (ctx) {
let params = {ctx: ctx, name: 'cbEvent2'}
cHelper(params, res => {
// doSomethingSuccess
}, res => {
// doSomethingFail
})
}
5. 进一步抽象
我们希望 clink.js
中回调的实现更加抽象,将其中业务逻辑 doSomthingForBiz(比如一些页面元素的操作)抽离出来到biz.js中,而clink.js只包含一些公共逻辑 doSomthingForCommon。所以我们在 biz.js
中写业务代码时,也实现回调函数,因此在clink.js中要做如下修改来实现方法的继承:
//biz.js
import clink from './clink'
class Action {
constructor () {
super()
clink.cbEvent1(this)
clink.cbEvent2(this)
}
// 实现Event1回调函数
cbEvent1 (res) {
// doSomethingForBiz
}
// 实现Event2回调函数
cbEvent2 (res) {
// doSomethingForBiz
if (res.code === 0) {
// doSomethingSuccessForBiz
}
else {
/ /doSomethingFailForBiz
}
}
}
// clink.js
const cHelper = (args, successCallback, failCallback) => {
let retryCount = 0
const maxRetryCount = args.retry || 3
return root[args.name] = res => {
const context = args.ctx
const success = parseInt(res.code, 10) === 0
// biz中是否有定义callback
const isOverride = context && (context[args.name] instanceof Function)
if (!success && (retryCount < maxRetryCount)) {
args.doEvent && (args.doEvent instanceof Function) && args.doEvent()
retryCount++
}
else if (!success && (retryCount >= maxRetryCount)) {
failCallback(res)
// 执行biz中的callback
isOverride && context[args.name].call(context, res);
}
else {
successCallback(res)
// 执行biz中的callback
isOverride && context[args.name].call(context, res)
}
}
}
/**
* Event1回调函数(有公共逻辑)
*/
clink.cbEvent1 = function (ctx) {
let me = this
let params = {ctx: ctx, name: 'cbEvent1'}
cHelper(params, res => {
// doSomethingSuccessForPauseCommon
}, res => {
// doSomethingFailForPauseCommon
})
}
/**
* Event2回调函数(无公共逻辑)
*/
clink.cbEvent2 = function (ctx) {
let me = this
let params = {ctx: ctx, name: 'cbEvent2'}
cHelper(params, res => true, res => true)
}
这样一来,执行完 doSomethingForCommon 后会执行 doSomethingForBiz
6. 总结
至此,已经完成了全部的重构封装。clink.js
是对sdk的封装,服务于业务biz1
, biz2
, ...
,其中cHelper
函数是 clink.js
中其他callback的高阶封装。