1

I am looking for possible solutions to chain promises just like shown below.

const getDetailsFromDb = await prisma.someTableNames().field()

Because the only possible way I know is as follows:

const SomethingToBeChained = function() {
   // where every method of object attached
   // to this instance should return this itself
   // so other properties attached to it can be accecd.
   return this
}
SomethingToBeChained.prototype = {
   methodOne: function () { ...do something...; return this }
   methodTwo: function () { ...do something...; return this }
}

// now one can do following.
SomethingToBeChained.methodOne().methodTwo()

But prisma client can do both how? Can anyone explain me how this works behind the scene of the prisma client. How does its structure achieve to call await and chain objects same time?

Only possible way I know ?

const somethingAwaited = (await SomethingToBeChainedIsPromise().wantToChainSomethingButCant()) <--- Ugly
const wantThis = await SomethingToBeChainedIsPromise().wantToChainSomethingButCant()
Titulum
  • 9,928
  • 11
  • 41
  • 79
dotsinspace
  • 661
  • 7
  • 21
  • Probably because `prisma.someTableNames()` does not return a promise, but some object that has as `.field()` method returning a promise? – Bergi Jan 18 '20 at 14:15
  • Btw, best practice in chaining is *not to* `return this`, but rather return a new object. – Bergi Jan 18 '20 at 14:16

2 Answers2

0

await can work with a thenable; it does not necessarily have to be an instance of the native Promise class/constructor.

So you could imagine that your prototype also has a then method:

SomethingToBeChained.prototype = {
   then: function (onFulfil, onReject) {
        // Here you could await some asynchronous database event
        // Once that comes back with success, call the onFulfil callback:
        setTimeout(onFulfil, 1000); 
   },
   methodOne: function () { ...do something...; return this },
   methodTwo: function () { ...do something...; return this }
}

With this extension, you can magically use await (it will actually call the then method with an internal callback argument). A little mock-demo:

let obj = {
     then(cb) {
         setTimeout(cb, 1000);
     }
}

async function test() {
    console.log("before");
    await obj;
    console.log("after");
}

test();

You could even make it to work with a real Promise object. In that case your this object should be an instance of the Promise class, extended with your own methods. But the idea remains the same.

trincot
  • 317,000
  • 35
  • 244
  • 286
  • Clever idea, but subtle behavior differences between your then and the promise.then could be confusing. There are rules about promise locking, error trapping and proppogation, identity, and associativity, and switching between map and flatMap behavior that make implementing a compliant .then a lot harder than it sounds. – Eric Elliott Jan 18 '20 at 11:28
  • 1
    @EricElliott, I am quite aware of that, as you can see in [this answer](https://stackoverflow.com/questions/23772801/basic-javascript-promise-implementation-attempt/42057900#42057900) I once gave. However the [Promises/A+ specification](https://promisesaplus.com/) is designed to work with simple "thenables" in its Promise Resolution Procedure. And EcmaScript is Promises/A+ compliant (including `await`). See also the thenable example given on [mdn](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/await) – trincot Jan 18 '20 at 11:40
0

In my opinion, the best way to approach this problem would be to have each method queue actions to be dispatched later. In other words, calling a method would be equivalent to declaring a function composition pipeline to be executed later.

Your calling code would need to change a little:

var wantThis = await somethingToBeChained().somethingElse().start()

In this scenario, all the methods return this except for the start() method, which actually applies the real functions and returns a promise.

This is similar to creating an async function composition pipeline:

const asyncPipe = (...fns) => x => fns.reduce(async (y, f) => f(await y), x);

That would produce a function that you can call after the fact to trigger the actual application of the function pipeline. Separating the function composition from the function application has benefits like better testability, better modularity, and easier debugging.

Eric Elliott
  • 4,711
  • 1
  • 25
  • 33