Open isayme opened 5 years ago
系统有 任务, 每个任务有一个执行者. 服务端提供API用户获取任务数据.
任务
执行者
// 用户 var UserSchema = new Schema({ name: String }) var User = mongoose.model('User', UserSchema) // 任务 var TaskSchema = new Schema({ name: String, _creatorId: { ref: 'User', type: Schema.Types.ObjectId }, _executorId: { ref: 'User', type: Schema.Types.ObjectId } }) var Task = mongoose.model('Task', UserSchema)
const task = await Task.findById(_id).exec()
const task = await Task.findById(_id).exec() const creator = await User.findById(task._creatorId).exec() // 注: 由于 mongoose 内部实现, task.toJSON() 后是没有此 creator 字段, 此处仅示例用, 下同. task.creator = creator
const tasks = await Task.find(conds).exec()
const tasks = await Task.find(conds).exec() await Promise.all(tasks.map(async function (task) { const creator = await User.findById(task.creatorId).exec() task.creator = creator }))
const tasks = await Task.find(conds).exec() // 也可以直接用 _.uniqBy let creatorIds = tasks.map(task => `${task._creatorId}`) creatorIds = _.uniq(creatorIds) const creators = await User.find({ _id: { $in: creatorIds } }).exec() const map = new Map() creators.forEach(creator => { map.set(`${creator._id}`, creator) }) tasks.forEach(task => { task.creator = map.get(`${task._creatorId}`) })
const DataLoader = require('dataloader') const tasks = await Task.find(conds).exec() // 注: 每个 request 单独 new 一个 loader, 比如初始化后赋值为 req.dataloader.user var userLoader = new DataLoader(ids => { // 示意用, 会存在问题, 详见 **特别须知** 中的实现 return User.find({ _id: { $in: ids } }).exec() }, { cacheKeyFn: id => `${id}` }) await Promise.all(tasks.map(async function (task) { let creator = await userLoader.load(task._creatorId) task.creator = creator }))
class DataLoader { constructor (batchLoadFn, { cacheKeyFn }) { this._batchLoadFn = batchLoadFn this._cacheKeyFn = cacheKeyFn this._batchQuene = [] this._cacheMap = new Map() } load (_id) { const cacheKey = this._cacheKeyFn(_id) if (this._cacheMap.has(cacheKey)) { return this._cacheMap.get(cacheKey) } else { const p = new Promise((resolve, reject) => { this._batchQuene.push({ _id, resolve, reject }) if (this._batchQuene.length === 1) { process.nextTick(() => { const queue = this._batchQuene this._batchQuene = [] this._batchLoadFn(queue.map(ele => ele._id)) .then(docs => { queue.forEach((ele, idx) => ele.resolve(docs[idx])) }) .catch(err => { queue.forEach(ele => ele.reject(err)) }) }) } }) this._cacheMap.set(cacheKey, p) return p } } }
const batchLoadFn = (model, ids) => { return model .find({ _id: { $in: ids } }) .exec() .then(docs => { // 返回数据必须和 ids.length 一致, 且映射顺序也需要保持一致 // refer: https://github.com/facebook/dataloader#batch-function const docMap = new Map() docs.forEach(doc => { docMap.set(`${doc._id}`, doc) }) return ids.map(_id => { return docMap.get(`${_id}`) || null }) }) }
背景
系统有
任务
, 每个任务有一个执行者
. 服务端提供API用户获取任务数据.需求1: 获取某个任务
需求2: 获取某个任务及创建者
需求3: 获取一组任务
需求4: 获取一组任务及创建者
问题:
改进思路:
问题:
改进思路:
优势
Polyfill
特别须知
batchLoadFn 要求返回数据长度必须和 ids 一致;
batchLoadFn 要求保障返回数据顺序和 ids 一致;
参考资料