|
function* getFullPost(id) { |
|
const post = yield ['getPost', id] |
|
const [author, comments] = yield* all([ |
|
getFullUser(post.authorId), |
|
all(post.commentIds.map(id => getFullComment(id))), |
|
]) |
|
return { |
|
...post, |
|
author, |
|
comments, |
|
} |
|
} |
|
|
|
function* getFullUser(id) { |
|
const user = yield ['getUser', id] |
|
return user |
|
} |
|
|
|
function* getFullComment(id) { |
|
const comment = yield ['getComment', id] |
|
const author = yield* getFullUser(comment.authorId) |
|
return { |
|
...comment, |
|
author, |
|
} |
|
} |
|
|
|
function* getData(postIds) { |
|
return yield* all(postIds.map(id => getFullPost(id))) |
|
} |
|
|
|
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
|
|
|
function* naiveAll(gens) { |
|
const res = [] |
|
for (const g of gens) { |
|
res.push(yield* g) |
|
} |
|
return res |
|
} |
|
|
|
function* batchedAll(gens) { |
|
let lengths = [], batches = [] |
|
let colengths = [], cobatches = [] |
|
let values = [], covalues = [] |
|
const results = new Map() |
|
|
|
do { |
|
for (const g of gens) { |
|
if (results.has(g)) continue |
|
|
|
const colength = colengths.shift() |
|
const cobatch = cobatches.shift() |
|
|
|
let covalue |
|
if (cobatch) { |
|
covalue = covalues.splice(0, colength) |
|
} else { |
|
covalue = covalues.shift() |
|
} |
|
const {done, value} = g.next(covalue) |
|
if (done) { |
|
results.set(g, value) |
|
continue |
|
} |
|
const [tag, arg] = value |
|
if (tag === 'batch') { |
|
lengths.push(arg.length) |
|
batches.push(true) |
|
values = values.concat(arg) |
|
} else { |
|
lengths.push(1) |
|
batches.push(false) |
|
values.push(value) |
|
} |
|
} |
|
|
|
if (results.size == gens.length) break |
|
|
|
covalues = yield ['batch', values] |
|
colengths = lengths |
|
cobatches = batches |
|
values = [] |
|
lengths = [] |
|
batches = [] |
|
} while(true) |
|
return gens.map(g => results.get(g)) |
|
} |
|
|
|
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
|
|
|
const db = { |
|
users: { |
|
1: {login: 'john'}, |
|
2: {login: 'jane'}, |
|
}, |
|
posts: { |
|
1: { |
|
title: 'a', |
|
authorId: '1', |
|
commentIds: ['1', '2'], |
|
}, |
|
2: { |
|
title: 'b', |
|
authorId: '2', |
|
commentIds: ['3', '4'], |
|
}, |
|
}, |
|
comments: { |
|
1: { |
|
text: 'q', |
|
authorId: '1', |
|
}, |
|
2: { |
|
text: 'w', |
|
authorId: '2', |
|
}, |
|
3: { |
|
text: 'e', |
|
authorId: '1', |
|
}, |
|
4: { |
|
text: 'r', |
|
authorId: '2', |
|
}, |
|
} |
|
} |
|
|
|
function wait(data, delay) { |
|
return new Promise((resolve, reject) => { |
|
setTimeout(() => resolve(data), delay) |
|
}) |
|
} |
|
|
|
function getResource(type, id) { |
|
//console.log("getResource: ", type, id) |
|
return wait( |
|
db[type][id], |
|
1000, |
|
) |
|
} |
|
|
|
function getResources(type, ids) { |
|
//console.log("getResources: ", type, ids) |
|
return wait( |
|
ids.map(id => db[type][id]), |
|
1000, |
|
) |
|
} |
|
|
|
const batchHandlers = { |
|
getUser: ids => getResources('users', ids), |
|
getPost: ids => getResources('posts', ids), |
|
getComment: ids => getResources('comments', ids), |
|
} |
|
|
|
const handlers = { |
|
getUser: id => getResource('users', id), |
|
getPost: id => getResource('posts', id), |
|
getComment: id => getResource('comments', id), |
|
batch: async values => { |
|
const argsByTag = {} |
|
const indexesByTag = {} |
|
for (const [idx, value] of values.entries()) { |
|
const [tag, arg] = value |
|
argsByTag[tag] ||= [] |
|
argsByTag[tag].push(arg) |
|
indexesByTag[tag] ||= [] |
|
indexesByTag[tag].push(idx) |
|
} |
|
const tags = Object.keys(argsByTag) |
|
const resp = await Promise.all( |
|
tags.map(tag => { |
|
const args = argsByTag[tag] |
|
return batchHandlers[tag](args) |
|
}) |
|
) |
|
const res = [] |
|
res.length = values.length |
|
for (const [respIdx, tag] of tags.entries()) { |
|
const indexes = indexesByTag[tag] |
|
for (let i = 0; i < indexes.length; i++) { |
|
res[indexes[i]] = resp[respIdx][i] |
|
} |
|
} |
|
|
|
return res |
|
} |
|
} |
|
|
|
async function handle(gen) { |
|
let done, value, covalue |
|
do { |
|
;({value, done} = gen.next(covalue)) |
|
if (done) return value |
|
const [tag, arg] = value |
|
covalue = await handlers[tag](arg) |
|
|
|
console.log('handle:', tag, arg, covalue) |
|
|
|
} while(true) |
|
} |
|
|
|
//const all = naiveAll |
|
const all = batchedAll |
|
|
|
handle(getData(['1', '2'])) |
|
.then(r => JSON.stringify(r, null, 2)) |
|
.then(r => console.log(r)) |