Robert Lemiesz
Robert Lemiesz

Reputation: 1156

Can I use data loader without batching

What I am really interested in with data-loader is the per request caching. For example say my graphql query needs to call getUser("id1") 3x. I would like something to dedupe that call.

However it seems like with data-loader I need to pass in an array of keys into my batch function, and multiple requests will be batched into one api calls.

This has me making a few assumptions that i dont like:

1.) That each service I am calling has a batch api (some of the ones im dealing with do not).

2.) What if multiple calls get batched into 1 api call, and that call fails because 1 of the items was not found. Normally I could handle this by returning null for that field, and that could be a valid case. Now however my entire call may fail, if the batch API decides to throw an error since 1 item was not found.

Is there anyway to use dataloader with single-key requests.

Upvotes: 2

Views: 3019

Answers (2)

Aki
Aki

Reputation: 1754

Daniel's solution is perfectly fine and is in fact what I've used so far, after extracting it into a helper function. But I just found another solution that does not require that much boilerplate code.

new DataLoader<string, string>(async ([key]) => [await getEntityById(key)], {batch: false});

When we set batch: false then we should always get a key-array of size one passed as argument. We can therefore simply destructure it and return a one-sized array with the data. Notice the brackets arround the return value! If you omit those, this could go horribly wrong e.g. for string values.

Upvotes: 0

Daniel Rearden
Daniel Rearden

Reputation: 84657

Both assumptions are wrong because the implementation of the batch function is ultimately left up to you. As indicated in the documentation, the only requirements when writing your batch function are as follows:

A batch loading function accepts an Array of keys, and returns a Promise which resolves to an Array of values or Error instances.

So there's no need for the underlying data source to also accept an array of IDs. And there's no need for one or more failed calls to cause the whole function to throw since you can return either null or an error instance for any particular ID in the array you return. In fact, your batch function should never throw and instead should always return an array of one or more errors.

In other words, your implementation of the batch function might look something like:

async function batchFn (ids) {
  const result = await Promise.all(ids.map(async (id) => {
    try {
      const foo = await getFooById(id)
      return foo
    } catch (e) {
      // either return null or the error
    }
  }))
}

It's worth noting that it's also possible to set the maxBatchSize to 1 to effectively disable batching. However, this doesn't change the requirements for how your batch function is implemented -- it always needs to take an array of IDs and always needs to return an array of values/errors of the same length as the array of IDs.

Upvotes: 2

Related Questions