本文已授权掘金开发者社区公众号独家使用,包括但不限于编辑、标注原创等权益。
简介
微信小程序消息订阅是大部分小程序场景都会用到的功能,作为基础开发时,这一类的API都应该提前被包装好,作为一个工具类库存在。方便团队成员订阅消息时使用。
在大多数情况下,订阅消息都只是配置项和中介方法不一样。前端只需要负责调用微信的API,然后通知后端订阅的具体模型就算是达到目的了,后端是通过订阅后发送的请求接口去发送对应的模板数据。
本位属于分享文,不涉及太多复杂的知识点,大部分前端都不存在阅读障碍。
微信小程序 & TaroJS
对于TaroJS 和 微信小程序来说本体上代码并不会变化太多,所以理论上通用,如果出现问题的话,大概率是微信小程序上部分ES API不支持,进行简单的Polifill解决80%的问题。
可以看下它们的API调用方式:
=> 微信
wx.requestSubscribeMessage({
tmplIds: [''],
success (res) { }
})
TaroJS =>
Taro.requestSubscribeMessage({
tmplIds: [''],
success: function (res) { }
})
可以看到,两者API做到了完美还原,除了来源方不一致的情况下,大部分都是一样的,甚至Taro除了支持回调函数的形式外,函数的返回形式是一个Promise,这意味着我们可以通过使用Promise Status回调来进行错误和成功的代码回调。
推荐在
TaroJS使用Promise来进行回调,部分接口(支付)会在回调函数后,依旧throw error,导致错误埋点监控频繁出现。
如何配置数据源
这里数据源分为两块,一块是当前的模板ID, 另一块是请求参数,每一次订阅至少有一个模板ID和一次接口请求,这一类属于固定参数。
推荐创建一个config.js单独保存这一类的东西。
// config.js
subScriptionTemplateConfig = {
test: ['templateId']
}
subScriptionRequestConfig = {
test: [{ a: 1, b: 1 }]
}
声明入口函数
入口函数onSubscriptionTick作为一个调用主函数main需要接口两个数组参数,一个是模板id列表,另一个是请求参数列表。开始之前,会对参数做一个校验,如果不符合那就throw error出去,直接走catch status,如果校验过了,那么就可以进行消息订阅。这这里需要注意的是,为了函数不太过复杂,消息订阅也抽出去了一个方法,专门做用户订阅结果处理
/**
* 消息订阅公共消息类
* @param { Array<string> } templateId 微信模板ID
* @param { Array<Object> } paramList 订阅参数
* @exports utils/onSubscriptionTick
* @example
* onSubscriptionTick(['templateid'], [object: OBject]).then()
* @returns { Promise<any> } 当前订阅状态
*/
export async function onSubscriptionTick(templateId = [], paramList = []) {
if (!templateId) {
throw new Error("throw error: 微信订阅模板id不存在");
}
if (!paramList) {
throw new Error("throw error: 微信订阅请求参数不存在");
}
if (templateId.length !== paramList.length) {
throw new Error("throw error: 订阅模板的消息和请求参数不一致");
}
const asyncCurrentRequestList = [];
await subscriptionStatus(templateId)
.then((res) => {
// 订阅成功后, 需要发送消息
res.forEach((currentMessage) => {
asyncCurrentRequestList.push(
putSubscribeNews(paramList[currentMessage?.successIndex])
);
});
if (asyncCurrentRequestList.length > 0) {
promiseAllSettled(asyncCurrentRequestList).then((result) => {
for (let index = 0; index < result.length; index++) {
if (result[index].status === "rejected") {
throw new Error(
"throw error: 消息订阅接口失败,订阅任务调度失败"
);
}
}
return true;
});
} else {
throw new Error("throw error: 接口请求参数不存在");
}
})
.catch((err) => {
// 订阅失败回调
throw err;
});
}
消息订阅的处理
需要注意的是,微信小程序当有多个订阅消息存在的时候,如果用户取消了其中一个,需要对成功的订阅消息ID进行过滤,尽可能保证通知给后端接口的准确性。
有A,B,C三条消息需要订阅,用户选择了B,C的需求,这个时候就需要代码做一次坚持,看看哪些模板ID是accept, 非accept都不是成功的订阅,因此,我们只需要将key为模板ID属性的值判断以下是不是accept就可以了。
/**
* 处理微信回调信息
* @param { Array<string> } templateIds 微信模板ID列表
* @returns { Promise<any> }
*/
function subscriptionStatus(templateIds = []) {
return new Promise((resolve, reject) => {
Taro.requestSubscribeMessage({
tmplIds: templateIds,
}).then(res => {
// 判断当前任务状态完成的状态
const successTemplateResult = []
templateIds.forEach((id, index) => {
if (res[id] === 'accept') {
successTemplateResult.push({
successId: id,
successIndex: index,
})
}
})
// 检测订阅id长度是否大于0, 如果当前允许订阅的id为0,那么也算是订阅失败
if (successTemplateResult.length > 0) {
resolve(successTemplateResult)
} else {
reject(new Error('订阅错误:当前订阅没有被允许的消息记录'))
}
}).catch(err => {
// throw error
reject(err)
})
})
}
并发请求,只接口状态
Promise新增加了一个方法叫做Promise.allSettled,它的作用是执行多个Promise,将其结果和状态汇聚成一个回调,不论是成功还是失败。不得不说是一个非常方便的接口,但是考虑到兼容性,所以我们决定手写一个Promise任务队列来实现。实现代码非常的简单,单纯的做为结果返回。方便后续判断订阅状态。
如果当Promise.allSettled的结果集状态存在rejected的时候,就说明有接口请求失败了,那么这个消息其实是一个订阅失败的,虽然用户订阅了前端消息,但是后端永远都不用给他发送的。
/**
* Promise.allSettled方法,
* @param { Array<Promise<any>> } promises 需要执行的Promise堆栈
* @returns { Promise<Array<any>> } promise输出集合
*/
export function promiseAllSettled(promises) {
let resultArr = []
let runtimeCount = 0
console.log('当前发送的消息', promises)
return new Promise((resolve) => {
function finish(){// 执行结果
if (runtimeCount == promises.length){
resolve(resultArr)
}
}
promises.forEach((promiseTask) => {
promiseTask.then((data) => {
resultArr.push({
status: 'fulfilled',
value: data,
})
runtimeCount++
finish()
}, (data) => {
resultArr.push({
status: 'rejected',
value: data,
})
runtimeCount++
finish()
})
})
})
}
可以简单点的优化
-
subscriptionStatus方法,其实不需要new Promise,只需要通过async做一层包装,将出现错误的地方throw error抛出,正确结果return出去就好了,会更加的优雅。 -
finally在promiseAllSettled中为什么通过一个callback实现,经过测试,在某些定制机的一个环境下,finally可能找不到,因此,选择兼容性更好的then & catch进行处理。 -
判断
rejected的方法其实也可以拆分成一个专门的函数,如果细心的朋友发现了,这一块代码的圈复杂度会在一个19左右,虽然能看懂,但其实还可以进一步分方法进行。将onSubscriptionTick当作一个主函数来进行。
总结
代码来自于团队业务逻辑开发分享,代码并不是很难,主要是如果你正在开发小程序,其实没必要在去写一套方案了,拿来开箱即用,节省下开发的时间效率,快速过滤业务逻辑。
不过在闲暇时,可以来杯咖啡,仔细看下代码哦,Copy的代码最好还是了解下当前的执行逻辑和一些基本流程,这样能够快速的定位到错误发生的地方。
本次分享算是一个业务小知识,如果对你有帮助可以进行点个赞支持一下作者哦。