用户
 找回密码
 立即注册

QQ登录

只需一步,快速开始

扫一扫,登录网站

小程序社区 首页 教程 实战教程 查看内容

开箱即用的微信小程序订阅消息代码封装

Rolan 2021-1-15 16:02

微信小程序消息订阅是大部分小程序场景都会用到的功能,作为基础开发时,这一类的API都应该提前被包装好,作为一个工具类库存在。方便团队成员订阅消息时使用。 ...

简介

微信小程序消息订阅是大部分小程序场景都会用到的功能,作为基础开发时,这一类的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的需求,这个时候就需要代码做一次坚持,看看哪些模板IDaccept, 非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出去就好了,会更加的优雅。

  • finallypromiseAllSettled中为什么通过一个callback实现,经过测试,在某些定制机的一个环境下,finally可能找不到,因此,选择兼容性更好的then & catch进行处理。

  • 判断rejected的方法其实也可以拆分成一个专门的函数,如果细心的朋友发现了,这一块代码的圈复杂度会在一个19左右,虽然能看懂,但其实还可以进一步分方法进行。将onSubscriptionTick当作一个主函数来进行。


总结

代码来自于团队业务逻辑开发分享,代码并不是很难,主要是如果你正在开发小程序,其实没必要在去写一套方案了,拿来开箱即用,节省下开发的时间效率,快速过滤业务逻辑。

不过在闲暇时,可以来杯咖啡,仔细看下代码哦,Copy的代码最好还是了解下当前的执行逻辑和一些基本流程,这样能够快速的定位到错误发生的地方。


作者:wangly19
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
鲜花
鲜花
鸡蛋
鸡蛋
分享至 : QQ空间
收藏