最近几天在拿node搞graphql,用了dataloader来解决n+1问题。写loader的时候把loader放到了全局,当时内心就烦嘀咕,要是两个请求一块儿打过来,dataloader返回的数据会不会混淆? 好比如下这种状况:node
const pageLoader = new DataLoader(async (ids: string[]) => {
return controller.getPages(ids);
});
复制代码
假如两个请求的id有重合,一个ids=[1,2], 另一个ids=[2,3],若是dataloader把ids合并成[1,2,3]去请求,那返回的结果咋办?git
搞数据试了几回,发现dataloader会在第一个请求完成以后再去load第二个请求,并不存在我上面脑补的状况。 那问题又来了,dataloader咋搞的?github
翻了下dataloader的源码,在调用loader.load(id)的时候,会作两件事:promise
this._queue.push({ key, resolve, reject });
// Determine if a dispatch of this queue should be scheduled.
// A single dispatch should be scheduled per queue at the time when the
// queue changes from "empty" to "full".
if (this._queue.length === 1) {
if (shouldBatch) {
// If batching, schedule a task to dispatch the queue.
enqueuePostPromiseJob(() => dispatchQueue(this));
} else {
// Otherwise dispatch the (queue of one) immediately.
dispatchQueue(this);
}
}
var enqueuePostPromiseJob =
typeof process === 'object' && typeof process.nextTick === 'function' ?
function (fn) {
if (!resolvedPromise) {
resolvedPromise = Promise.resolve();
}
resolvedPromise.then(() => process.nextTick(fn)); // 关键, 任务被放到nextTick来执行了
} :
setImmediate || setTimeout;
复制代码
能够看到,收集queue里id的任务被放到nextTick里面来执行了。 也就是说,在nextTick回调执行以前load的id会被放到queue里做为一个id集合来处理,而node在处理下一个请求以前,就调用了nextTick的回调。因此 两个请求的id是不会混合的。。浏览器
但问题又来了,两个request的回调函数应当是放在事件循环的Poll Phase里面的,在处理完这个phase的队列以前,nextTick为何会被调用?bash
翻了n多篇文章,大概讲的都是node维护了一个nextTickQueue,libuv在每一个phase即将结束,要进入下个phase以前,会检查nextTickQueue里的回调并执行。async
而后看到了这篇文章,讲node 11.0以后, 为了和浏览器一致,在macrotask执行完以后都会去执行microtask。 这个理论说得通,但问题是我用的是node 8,因此应该不是由于这个缘由。ide
最后去提了一个issue,大神给的解答大概就是说nexttick和eventloop压根儿不搭嘎,eventloop每次跑完一个handler都会跑nexttick。 大概就这样吧,也不想深究了,毕竟node 11之后,感受已经彻底能够把nexttick看成一个microtask来看了。函数