「不合时宜」就想轮询整张表,怎么做?

需求很急

根据几年与用户「亲切友好」的交谈,经常看见用户在工单或者论坛提出一个需求:

我们这边要做一个数据修正,需要在云引擎里面轮询 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);
	});
}
});

造福后来人

好了,这段代码基本可以工作,但是可以思考一下几个问题:

  1. 假如我要求整个逻辑执行完毕之后给一个 Promise 类型的返回值怎么办?这个循环改成队列不是更好么?
  2. 这一次只是简单的加 tag,下一次我又要轮训这张表,但是可能要删除某一个属性,我能不能拓展一下,让其他人可以在项目里面可以针对 Todo 对象的操作进行拓展?
  3. 是不是可以拓展一下,让这个简单的代码可以支持其他表的轮询呢?

解决问题

首先针对第一个问题: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 分钟,我估摸着基本属于够用,但是不是最佳解决方案。

感谢所有对轮询这么执着的用户,其他语言的版本就不再献丑了。

晚安。

2 人赞了这个帖子.

Python的: