Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[QUESTION] Is possible chain two dataloaders? #229

Open
Tracked by #297
sneznaovca opened this issue Nov 20, 2019 · 1 comment
Open
Tracked by #297

[QUESTION] Is possible chain two dataloaders? #229

sneznaovca opened this issue Nov 20, 2019 · 1 comment

Comments

@sneznaovca
Copy link

Hi, is it possible chain two dataloaders? I have one resolver where I need using two dataloaders, is it possible? I can't use SQL join because it's slow.

async (row, _, { dataSources }) => {
    const partialData = await dataloader1(dataSources).load(row.id);
    if (partialData === null) {
        return null;
    }
    return await dataloader2(dataSources).load(partialData.id);
}
@klausXR
Copy link

klausXR commented Aug 26, 2020

A couple of weeks ago I was experimenting with something and I needed to do something similar to this, I came up with this solution

class MyDataLoader extends DataLoader {
    constructor(batchLoadFn, options) {
        // Initialize the DataLoader class
        super(batchLoadFn, options)

        // A custom function to run before .load resolves.
        // It doesn't do anything by default, it just returns what 
        // was passed to it
        this._beforeResolveFn = row => row
    }

    // Overwrite the default load handler, so that we have a chance
    // to run our custom method
    // .loadMany is just a wrapper around .load, which is why we only need to
    // overwrite this one.
    async load(key) {
        // Call the original load method, passing in the key,
        // then run our middleware function, and after it resolves,
        // return the result to the caller.

        // You can imagine that we are trying to resolve a Many to Many relationship,
        // where the result of calling super.load returns an array of foreign keys.
        // We can then pipe them through another dataloader to get the actual objects
        return super.load(key)
           .then(this._beforeResolveFn)
    }

    // Sets a custom function to run before the .load promise resolves
    async beforeResolve(fn) {
        this._beforeResolveFn = fn
    }
}

// Arbitrary usage
const ProductLoader = new MyDataLoader(...) // accepts productId
const CategoryLoader = new MyDataLoader(...) // accepts categoryId

// accepts productId, returns a list of categoryId's, like
// `SELECT * FROM productsCategories pc WHERE pc."productId" IN (...)` 
const ProductCategoryLoader = new MyDataLoader() 

// We know that the ProductCategoryLoader returns us an array of ids,
// So we pipe its result through the CategoryLoader, and after that resolves
// .load will return an array of categories to the ProductCategoryLoader.load callee
ProductCategoryLoader.beforeResolve(categoryIds => CategoryLoader.loadMany(categoryIds))

ProductCategoryLoader.load(1) // Product id
  .then(categories => {...} ) // An array of categories 

I figured it would somewhat simplify the more complex use-cases.

It requires extending the base class, but its a transparent wrapper by default. Also, I just wrote this in a text editor, didn't test this specific implementation, because I don't have access to my code at the moment, but the general idea is the same ( in case it doesn't work after copy/paste ).

@saihaj saihaj mentioned this issue Mar 11, 2022
26 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants