Skip to content

Instantly share code, notes, and snippets.

@xialvjun
Last active December 5, 2017 05:46
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save xialvjun/3c218668a32c932c35684911c7305ae9 to your computer and use it in GitHub Desktop.
Save xialvjun/3c218668a32c932c35684911c7305ae9 to your computer and use it in GitHub Desktop.
just a sample to show how to implement a js dataloader like facebook/dataloader
const DataLoader = require('dataloader');
const { knex } = require('./singleton.js');
function middleware(ctx, next) {
const target = {};
const handler = {
get: function (receiver, table_name$key_column_name$) {
if (receiver[table_name$key_column_name$]) {
return receiver[table_name$key_column_name$];
}
const [table_name, key_column_name, single] = table_name$key_column_name$.split('$');
const is_single = single === undefined;
const loader = receiver[table_name$key_column_name$] = new DataLoader(async keys => {
const objs = await knex(table_name).whereIn(key_column_name, keys);
if (is_single) {
return keys.map(key => objs.find(obj => obj[key_column_name] === key) || null);
}
return keys.map(key => objs.filter(obj => obj[key_column_name] === key));
});
return loader;
},
};
// 用 Proxy 得到在运行时直接根据调用属性名得到 dataloader,用 memoize 在一次请求中保留 dataloader。。。
// 这里的 dataloader 都只 load 一张表,因为根据多表 load 比较麻烦,而且会用到 join,性能低。。。不过可以后续研究一下。。。
// 最好也只用单表 dataloader。这样可能更好,更清晰。一对多关联,就两个 dataloader 就是;多对多关联,那就三个。清晰的很。。。。
// 而且也能很好的 dataloader 重用,如果使用跨表 dataloader,可能随着 load 的方向的不同,最终的 dataloader 数量比单表 dataloader 还多。。。
// 属性名规则:table_name$key_column_name$
// 用 美元符号 $ 分隔。。。如果结尾是 $,则认为是获取数组,即 loader.load(id) 结果是数组,即该字段是外键,且可重复。。。
// 这里可能使用 _by_ 比用 $ 更好:
// 用 _by_ 太长,不过更语义化,但是还要用个东西表示复数;
// 用 $ 倒是短,但是双击选择麻烦,而且 $ 在很多语言中不是合法标识符。
ctx.loaders = new Proxy(target, handler);
return next();
}
module.exports = middleware;
function new_dataloader(fn) {
let to_resolve_promise = {}
let cache = {}
let started = false
return {
load(id) {
if (cache[id]) {
return Promise.resolve(cache[id]);
}
if (!started) {
setTimeout(function() {
let ids = Object.keys(to_resolve_promise)
fn(ids).then(rs => {
if (ids.length !== rs.length) {
ids.forEach(id => to_resolve_promise[id].forEach(p => p.reject('长度不一致')))
} else {
ids.forEach((id, index) => {
cache[id] = rs[index]
to_resolve_promise[id].forEach(p => p.resolve(rs[index]))
})
}
})
}, 0);
}
return new Promise((resolve, reject)=>{
to_resolve_promise[id] = [].concat(to_resolve_promise[id]).concat({resolve, reject})
});
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment