云对象 - 重新定义前后端交互

1,160 阅读5分钟

题记:

很多人都在讨论全栈。全栈就是使用一种语言js吗?不止。

语言的统一是第一步,下一步是协作的改善。

这样才能让语言统一发挥更大的价值。

背景

从2000年开始,xml作为数据交换格式开始流行,服务器拼接xml接口,客户端js获取xml内容,动态修改页面。

几年后,数据量更小的json替代了xml

移动互联网到来后,因为客户端分裂,加剧了接口的泛滥。

一转眼,接口已经玩了20年了。其他技术飞速发展,而前后端交互却一直是接口,没有什么创新。

js已经有了importexport,为什么调用后端接口,不能像调用一个前端模块一样呢?

serverless,让这一切开始了变化。

亚马逊lambda提出了云函数的概念,不再使用restfulurl,但仍然是基于json交换前后端数据。

uniCloud最初也以支持云函数为开始,但我们发现这仍不够优雅、简洁、直观、统一。

HBuilderX 3.4 开始,uniCloud推出了“云对象”,让调用云端服务,真正变成像调用前端模块一样简单。

什么是云对象

云对象:服务器编写API,客户端调用API,不再开发传输json的接口。思路更清晰、代码更精简。

比如服务端编写一个云对象news,该对象导出若干方法:add()、getList()、getDetailById()、softDel()、changeContent()、allowPublic()、addComment()、getComments()...等方法。

客户端的js则可以直接import这个news云对象,然后直接调用add等方法。

服务器示例代码如下:

HBuilderX中在uniCloud/cloudfunctions目录新建云函数/云对象,选择类型为云对象,起名为news。打开云对象入口index.obj.js,添加一个add方法。

// 云对象名:news
module.exports = {
	add(title, content) {
		title = title.trim()
		content = content.trim()
		if(!title || !content) {
			return {
				errCode: 'INVALID_NEWS',
				errMsg: '标题或内容不可为空'
			}
		}
		// ...其他逻辑
		return {
			errCode: 0,
			errMsg: '创建成功'
		}
	}
}

然后在客户端的js中,import这个news对象,调用它的add方法。

const news = uniCloud.importObject('news') //第一步导入云对象
async function add () {
	try {
		const res = await news.add('title demo', 'content demo') //导入云对象后就可以直接调用该对象的方法了,注意使用异步await
		uni.showToast({
			title: '创建成功'
		})
	} catch (e) { // 符合uniCloud响应体规范 https://uniapp.dcloud.net.cn/uniCloud/cf-functions?id=resformat,自动抛出此错误 
	}
}

可以看到云对象的代码非常清晰,代码行数也只有27行。

而同样的逻辑,使用传统的接口方式则需要更多代码,见下:

// 传统方式调用云函数-云函数代码
// 云函数名:news
// 云函数入口index.js内容如下
'use strict';
exports.main = async (event, context) => {
	const {
		method,
		params
	} = event
	switch(method) {
		case 'add': {
			let {
				title,
				content
			} = params
			title = title.trim()
			content = content.trim()
			if(!title || !content) {
				return {
					errCode: 'INVALID_NEWS',
					errMsg: 'NEWS标题或内容不可为空'
				}
			}
			// ...省略其他逻辑
			return {
				errCode: 0,
				errMsg: '创建成功'
			}
		}
	}
	return {
		errCode: 'METHOD_NOT_FOUND',
		errMsg: `Method[${method}] not found`
	}
};

传统方式调用云函数-客户端代码

async function add () {
	try {
		const res = uniCloud.callFunction({
			name: 'news', 
			data: {
				method: 'add',
				params: {
					title: 'title demo',
					content: 'content demo'
				}
			}
		})
		const {
			errCode,
			errMsg
		} = res.result
		if(errCode) {
			uni.showModal({
				title: '创建失败',
				content: errMsg,
				showCancel: false
			})
			return
		}
		uni.showToast({
			title: '创建成功'
		})
	} catch (e) {
		uni.showModal({
			title: '创建失败',
			content: e.message,
			showCancel: false
		})
	}
}

以上传统开发需要68行代码,对比云对象的27行代码,可以说“又丑又长”

更多强大功能

1. 预处理与后处理

无论请求云对象的哪个方法,开始前都会经过_before方法,结束会执行_after方法。这有助于统一的提前校验和格式化错误。

示例:

// news/index.obj.js
module.exports = {
	_before: function(){
		this.startTime = Date.now() // 在before内记录开始时间并在this上挂载,以供后续流程使用
		const methodName = this.getMethodName() // 获取当前请求的云对象方法名
		if(methodName === 'add' && !this.getUniIdToken()) {
			throw new Error('token不存在')
		}
	},
	add: function(title = '', content = '') {
		if(title === 'abc') {
			throw new Error('abc不是一个合法的news标题')
		}
		return {
			errCode: 0,
			errMsg: '创建成功'
		}
	},
	_after(error, result) {
		if(error) {
			throw error // 如果方法抛出错误,也直接抛出不处理
		}
		result.timeCost = Date.now() - this.startTime
		return result
	}
}

2. 云对象的返回值兼容uniCloud响应体规范

云对象返回值默认为uniCloud响应体规范,方便客户端统一拦截错误。

无论网络异常,还是token过期,都可以统一拦截提示。

详见uniCloud响应体规范

3. 自动显示交互界面

每次写客户端联网的代码时,开发者都免不了重复写一堆代码:先调用loading等待框,联网结束后再关闭loading,如果服务器异常则弹出提示框。

原来的写法(22行):

uni.showLoading({
	title: '联网中...'
})
uni.request({
	url: "xxxx",
	success: (res) => {
		uni.showToast({
			title: '添加成功',
			icon: 'success',
			mask: true,
			duration: duration
		});
	},
	fail: (err) => {
		uni.showModal({
			content: err.errMsg,
			showCancel: false
		});
	},
	complete: () => {
		uni.hideLoading();
	}
});

现在,调用云对象的方法时,默认自带上述功能。

  • 在请求联网开始时显示loading等待框,
  • 结束后隐藏loading
  • 如果请求报错,显示弹窗(也可配置为显示Toast)
const news = uniCloud.importObject('news') //第一步导入云对象
try {
	await news.add('title demo', 'content demo') //导入云对象后就可以直接调用该对象的方法了,注意使用异步await
	uni.showToast({
		title: '添加成功'
	})
} catch (e) {}

如上,原来需要23行的代码,现在7行就可以搞定!

当然,这些UI策略都可以自定义。

  1. URL化

为了历史兼容考虑,云对象同时提供了URL化方案。开发者仍然可以把一个云对象转换为一个httpurl接口。

总结

使用云对象带来的诸多好处:

  1. 更清晰的逻辑
  2. 更精简的代码
  3. 更少的协作成本(以及矛盾~)
  4. 客户端调用时在ide里有完善的代码提示,方法参数均可提示。(传输json可没法在ide里提示)
  5. 自动支持uniCloud响应体规范,方便错误拦截和统一处理

更多云对象介绍,参见:uniapp.dcloud.net.cn/uniCloud/cl…