cancel
Showing results for 
Search instead for 
Did you mean: 

CAP and ASYNC/AWAIT

Cassaro
Explorer

Basic issue here. I´m having a hard time trying to implement schema.js file. The thing is that when I try to use some async feature such as "await cds.read" or "SELECT.from... " or any async function I cannot manage to make it work properly. The handler ends and every logic after the "await cds.read" runs only after the promise is resolved and the response is already gone ( I told you it was a basic issue ).

So I found this nice blog from our good fellow DJ Adams:

https://blogs.sap.com/2019/08/21/computed-field-example-in-cap/comment-page-1

(I put a comment there)

DJ shows us an example which makes the thing I want to do: implement the after read handler making async calls (await cds.transaction(req).run(SELECT .from(Books) ...) and changing the final response based on the result from this async select. So I implemented it. But strange things are happening. The weird thing is that it sometimes fills the numberOfBooks attribute but most of the times it doesnt. I keep on hitting the refresh button and it keep on changing the results. Here are two responses, one after the other, without reseting or changing anything:

<code>{
@odata.context: "$metadata#Authors",
value: [
{
ID: 101,
name: "Emily Brontë",
numberOfBooks: 1
},
{
ID: 107,
name: "Charlote Brontë",
numberOfBooks: null
},
{
ID: 150,
name: "Edgar Allen Poe",
numberOfBooks: 2
},
{
ID: 170,
name: "Richard Carpenter",
numberOfBooks: null
}
]
}

(hit refresh…)

<code>{
@odata.context: "$metadata#Authors",
value: [
{
ID: 101,
name: "Emily Brontë",
numberOfBooks: null
},
{
ID: 107,
name: "Charlote Brontë",
numberOfBooks: null
},
{
ID: 150,
name: "Edgar Allen Poe",
numberOfBooks: null
},
{
ID: 170,
name: "Richard Carpenter",
numberOfBooks: null
}
]
}

Have you seen it? Edgar Allan Poe who once had 2 books the second time has none. Emily had one and now has none. The others didn´t even showed up the first time. Sometimes I get books for all of them, sometimes for some, but most of the times for none. Straaaange….

I suspect very strongly that it´s the same async/await issue I described above.

I though it was some problem with my installation (VSCode, nodejs, who knows…), so I tried it out on the SAP Business Application Studio, but only to get the same results. This rules out some outdated package os something else.

Has someone else seen things like these?

You can find my code right here:

https://github.com/ecassaro1/ws1/tree/master/p15

Any suggestions?

I´m pretty sure I´m missing something very basic...

Thanks in advance, Eric

Accepted Solutions (1)

Accepted Solutions (1)

SebastianEsch
Active Participant

Hi Eric,

you have to wrap your call to the Array.prototype.map() function into a Promise.all() call:

module.exports = srv => {

  const { Books } = srv.entities

  srv.after('READ', 'Authors', (authors, req) => {
    return Promise.all(authors.map(async author => {
      const publications = await cds.transaction(req).run(
        SELECT.from(Books).where({ author_ID: author.ID })
      )
      author.numberOfBooks = publications.length
    }));
  });
}

See the explanation here: https://flaviocopes.com/javascript-async-await-array-map/

Kind regards,

Sebastian

Cassaro
Explorer
0 Kudos

Wow! So it worked! You are ninja Sebastian!

Taking the lead from this, at the CAP page I see examples on these async calls but haven´t seen nothing like this "return / promise / map" aproach. Is this the way to go in order to dodge from the issue I mentioned at the top of this question?

Many thanks, Eric

SebastianEsch
Active Participant

Hi Eric,

I suggest looking at https://flaviocopes.com/javascript-async-await/ and https://flaviocopes.com/javascript-promises/ to understand async, await and promises.

The challenge is, that the Array.prototype.map() function returns a list of Promises that you want be to resolved before your after handler finishes. So you need to call Promise.all() to sync all those Promises that .map() creates.

If you would not use map(), you would not need Promise.all(), but you could just use async to declare your after handler as asynchronous and await to resolve the promise of cds.transaction().run() creates. Instead of await you cloud use then() to handle the result of the CDS query.

I guess it's personal preference or the organisations coding guidelines if you use await or then. 😉

Kind regards,

Sebastian

Cassaro
Explorer
0 Kudos

Adding to the topic:

Just realised that if we use the handler with the 'each' signature the await doesn´t awaits, like this:

  srv.after('READ', 'Ent1', async (each) => {
    ent2s = await cds.read('p16.Ent2').where({id:each.id})
    each.description = ent2s[0].description;   
  })

But nevertheless with the array signature it works fine, like this:

  srv.after('READ', 'Ent1', async (ent1s) => {
    for (let each of ent1s) {
      ent2s = await cds.read('p16.Ent2').where({id:each.id})
      each.description = ent2s[0].description;    
    }
  })
SebastianEsch
Active Participant

Hi Eric,

https://cap.cloud.sap/docs/node.js/api#service-after states "Only synchronous modifications are allowed. That is, replacements and asynchronous modifications like in the following two examples aren’t possible". The solution is "In order to replace/ asynchronously modify the result, use on handlers".

The documentation also explains the reasoning, why the after handler is limited in that way.

Kind regards,

Sebastian

nicoschoenteich
Developer Advocate
Developer Advocate

Hi all,

At the time of writing this (August 2023), I couldn't find any reference in the documentation that async operations shouldn't be done in an after handler. Maybe something changed since 2020. Also, the old link now gives a 404. I am using Sebastian Esch's approach and it works well.

Best, Nico

SebastianEsch
Active Participant

Looking at https://cap.cloud.sap/docs/node.js/core-services#srv-handle-event, after handlers should behave like before handlers now:

// after phase
await Promise.all (matching .after handlers)
if (req.errors) throw req.reject()

Kind regards,

Sebastian

Answers (0)