「不合时宜」就想轮询整张表,怎么做?
需求很急
根据几年与用户「亲切友好」的交谈,经常看见用户在工单或者论坛提出一个需求:
我们这边要做一个数据修正,需要在云引擎里面轮询 Todo 表,也就是传说中的获取整张表里面每一个数据,给每一个 Todo 对象添加对应的一个分类,但是这个分类是需要根据 Todo 的标题来进行提取的(也就说控制台添加一列,并且给予默认值的这个功能不太符合我的需求),怎么办?
但是基于 LeanCloud 里面 Query 类型的「基本法」:每一次查询最多支持设置 limit 的值为 1000,也就是说用户需要自己至少写一个循环(其他更好的方式后面会介绍),很多用户重复着下面的逻辑:
这段代码可以用
'use strict';
var AV = require('leanengine');
let query = new AV.Query('Todo');
// 先计算表的数据量
query.count().then(totalCount => {
let pageSize = 500;
let pageIndex = 0;
query.limit(pageSize);
query.skip(pageIndex * pageSize);
for (; pageIndex * pageSize < total; pageIndex++) {
query.limit(pageSize);
query.skip(pageIndex * pageSize);
query.ascending('createdAt');
query.find().then(function(todos) {
todos.map((todo, i, a) => {
let title = todo.get('title');
let tag = '...'; //generate from title
todo.set('tag', tag);
});
}, function(error) {
console.log(error);
});
}
});
造福后来人
好了,这段代码基本可以工作,但是可以思考一下几个问题:
- 假如我要求整个逻辑执行完毕之后给一个 Promise 类型的返回值怎么办?这个循环改成队列不是更好么?
- 这一次只是简单的加 tag,下一次我又要轮训这张表,但是可能要删除某一个属性,我能不能拓展一下,让其他人可以在项目里面可以针对 Todo 对象的操作进行拓展?
- 是不是可以拓展一下,让这个简单的代码可以支持其他表的轮询呢?
解决问题
首先针对第一个问题:async4
使用 async4 可以轻松创建队列,设置并发数等一系列通用常用好用的功能,我们将每一次 limit & skip 的操作都视为一个子任务,让 async4 去管理和执行。
第二个问题 和第三个问题实际上是一个问题,那就是代码中的 表的名字以及具体针对每一个单独的对象的操作 这两件事情都参数化,也就是调用方,传入表的名字(className) 和对象的操作(opration),剩下的时候就交由 async4 了,调用方就可以「坐享其成」了
实现代码
/*
className : 表的名称
orderKey : 执行队列的排序字段,例如按照 createdAt 逐个执行
filterArray :查询条件,例如可以是 {'name','lilei'} 最后执行的时候会按照 query.equalTo('name', 'lilei'); 进行查询,还可以传入 {key:'likes',value:10,where:'>'} 来匹配 query.greaterThan(key, value) 查询
opration : 操作每一个对象的函数,比如 avObj.set('age','18');
done : 当遍历全都执行完毕之后就会调用 done 函数
*/
'use strict';
var AV = require('leanengine');
var asyncjs = require("async");
function traverse(className, orderKey, filterArray, opration, done) {
const query = new AV.Query(className);
if (filterArray) {
for (let i = 0; i < filterArray.length; i++) {
let element = filterArray[i];
let key = element.key || '';
let value = element.value;
if (element.hasOwnProperty('where')) {
let where = element.where;
switch (where) {
case '>':
case 'greater':
{
query.greaterThan(key, value);
break;
}
case '<':
case 'less':
{
query.lessThan(key, value);
break;
}
case '<=':
case 'lessThanOrEqualTo':
{
query.lessThanOrEqualTo(key, value);
break;
}
case '>=':
case 'greaterThanOrEqualTo':
{
query.greaterThanOrEqualTo(key, value);
break;
}
case '!null':
case '!nil':
{
query.exists(key);
break;
}
case 'null':
case 'nil':
{
query.doesNotExist(key);
break;
}
default:
{
console.log('default');
query.equalTo(key, value);
}
}
} else {
query.equalTo(key, value);
}
}
}
var pageIndex = 0;
var pageSize = 100;
var total = 0;
var approved = 0;
var q = asyncjs.queue(function(task, callback) {
opration(task.item).then(function() {
approved++;
callback();
}, function(error) {
console.error(error);
if (error) throw error;
approved++;
callback();
});
}, 5);
q.drain = function() {
if (done) {
done();
}
console.log(className, 'end with total processed', approved);
}
query.count().then(function(c) {
console.log('total', c);
if (c == 0) {
done();
}
total = c;
let subQuery = query;
for (; pageIndex * pageSize < total; pageIndex++) {
subQuery.limit(pageSize);
subQuery.skip(pageIndex * pageSize);
subQuery.ascending(orderKey);
subQuery.find().then(function(objs) {
objs.forEach(function(element) {
q.push({
item: element
}, function(err) {
//console.log(className, element.id, approved);
});
}, this);
}, function(error) {
console.log(error);
});
}
}, function(error) {});
}
一点私心
整个这段代码从想法到写完不到 30 分钟,我估摸着基本属于够用,但是不是最佳解决方案。
感谢所有对轮询这么执着的用户,其他语言的版本就不再献丑了。
晚安。
-
创建时间
16年7月25日
-
最后回复
17年5月20日
-
2
回复
-
2.5K
浏览
-
3
用户
-
2
赞
-
3
链接