客户端 Meteor.call 等待服务端异步函数返回

在 Meteor 项目中,经常会有客户端使用 Meteor.call 方法去调用服务端的一个方法,并等待该方法返回。通常情况下,服务端的方法只需要 return 后,客户端使用回调函数就可以访问到 return 的值了。但如果服务端同样调用了一个异步执行的函数,那么此时就无法判断服务端的异步函数是否已经执行完毕,返回结果就会出现不准确的情况。如下所示:

服务端代码:

setWechatMenu: function (appId, appSecret, jsonMenu) {
	console.log(jsonMenu);
	// 创建微信菜单
	var url = "https://api.weixin.qq.com/cgi-bin/token?" +
	      "grant_type=client_credential&appid=" +
	      appId + "&secret=" + appSecret;

	Meteor.http.get(url, { timeout: 30000 }, function(err, result) {
		var jsonContent = JSON.parse(result.content);
		var accessToken = jsonContent.access_token;
		var url = "https://api.weixin.qq.com/cgi-bin/menu/create?access_token=" + accessToken;
		// post 创建菜单
		var response = Meteor.http.post(url, { data: jsonMenu }, function(err, result) {
			console.log('Content: ' + result.content);
		});
		return response;
	});
}

客户端代码:

Meteor.call("setWechatMenu", appId, appSecret, json, function(error, result) {
	if (!error) {
		console.log("result :", result);
	};
});

上面的例子中,我们在客户端使用 Meteor.call 方法调用了一个服务端的函数,等待服务端的异步函数 http.post 返回内容,但你会发现结果并不是那么理想,客户端在打印 result 的时候结果是 undefined。这是因为服务端 http.get 和 http.post 都使用了异步回调的方式取得返回值,实际这两个函数在调用时立即就返回了。而客户端也是立即就接收到了服务器的返回,并没有真正等到 http.get 和 http.post 执行完毕。

我开始天真的认为,只要在客户端使用同步方法调用 Meteor.call 不就可以了吗?

var result = Meteor.call("setWechatMenu", appId, appSecret, json);

但结果告诉我,这样是不行的,后来在 Meteor 官方查询文档也说到,这样的调用是有不确定性的,也就是跟使用异步回调处理是没什么区别的。而这种同步的方法仅在服务端(On the server)是有效的:

2015-11-15_113548

为了解决这种问题,Meteor 的 github issues 专门有一篇文章是介绍如何解决类似问题的:https://gist.github.com/possibilities/3443021

其中最简单的一个方法如下:

setWechatMenu: function (appId, appSecret, jsonMenu) {
	// 创建一个 future
	var Future = Npm.require('fibers/future');
	var fut = new Future();

	console.log(jsonMenu);
	// 创建微信菜单
	var url = "https://api.weixin.qq.com/cgi-bin/token?" +
	      "grant_type=client_credential&appid=" +
	      appId + "&secret=" + appSecret;

	Meteor.http.get(url, { timeout: 30000 }, function(err, result) {
		var jsonContent = JSON.parse(result.content);
		var accessToken = jsonContent.access_token;
		var url = "https://api.weixin.qq.com/cgi-bin/menu/create?access_token=" + accessToken;
		// post 创建菜单
		var response = Meteor.http.post(url, { data: jsonMenu }, function(err, result) {
			console.log('Content: ' + result.content);
		});
		// 将 response 传递给 fut 的成员对象
		fut.return(response);
	});
	// 等待 fut 被赋值后再返回
	return fut.wait();
}

更多的方法可以参考上面 issues 链接。

评论