0

I'm calling an Apex method imperatively inside a wire function. The wire function is used to retrieve listview and it's data. I'm using data from this listview to retrieve pricebookentry fields. getPricebookEntry is calling an apex method to retrieve pricebookentry. I'm passing all this data into a lightning datatable. But what is happening is the adding columndata is occuring before the apex method call the column data is becoming undefined. How do I make sure apex call occurs first and then the columndata code. I'm new to this. Sorry if I haven't been clear. I'm pasting the code below for better understanding.

@wire(getListUi, {objectApiName: PRODUCT_OBJECT,listViewApiName: '$Value'})
lastView({error,data}){
    if (data) {
        var columnData = [];
        var pb1,pb2,pb3,stpb,sup,tes;
        var santosh=0;
        this.allProductColumns=data.records.records;
        for(var i=0;i<this.allProductColumns.length;i++){
            getPriceBookEntry({productId: this.allProductColumns[i].id})
            .then( r =>{
                console.log('r',r);
                r.forEach(p =>{
                    if(p.Pricebook2Id=="01s46000004XMWhAAO"){
                        this.tes=p.UnitPrice;
                        console.log(this.tes);
                    }else if(p.Pricebook2Id=="01se0000000sup8AAA"){
                        this.stpb=p.UnitPrice;
                    }
                })
            })
            .catch(error =>{
                this.message = 'Error received: code ' + error.errorCode + ', ' + 'message ' + error.body.message;
                console.log(this.message);
            });
            columnData.push({ "ProductCode" : this.allProductColumns[i].fields.ProductCode.value,
                              "Name" : this.allProductColumns[i].fields.Name.value, 
                              "Family" : this.allProductColumns[i].fields.Family.value,
                              "01s46000004XMWhAAO" : this.tes,
                              "01se0000000sup8AAA" : this.stpb
                              });
        }
        this.allProductColumns=columnData;
    } else if (error) {
        this.error = error;
    }
}

Here tes,stpb,pb1,pb2... variables are pricebookentry prices which get updated after apex call. But the columndata.push is occuring before the apex call.

What to do? I don't know whom to ask. Someone please answer:(

  • Have you tried moving columnData.push just below the for each loop? Also noticed, there are other things like hardcoded id's in code, you might want to correct as a best practice – Hemant Jain Jul 18 at 15:34
  • Hey @HemantJain. Thanks for responding. I tried moving the columnData.push below the for each loop. It's working but the columns for only that iteration are being displayed. How do I append each iteration column data? – Nikhil Rayala Jul 19 at 8:20
  • Move it inside the for loop, for each iteration – Hemant Jain Jul 19 at 8:49
  • @HemantJain It is inside for loop and for each iteration – Nikhil Rayala Jul 19 at 10:08
  • Could you please edit the post and add your updated code? – Hemant Jain Jul 19 at 15:04
1

You need to re-visit how Javascript Promises work. The Apex method call getPriceBookEntry({productId: this.allProductColumns[i].id}) returns a Promise. Logic in the then block is guaranteed to execute after the Promise is resolved.

A Promise is basically an asynchronous method call, which will finish execution some time after your current method is executed. In your case the Apex method is actually called after your Javascript method.

I believe this should do what you're trying to achieve:

// never used them in lwc - or may lose context, so added this just in case
const ctx = this;
getPriceBookEntry({productId: this.allProductColumns[i].id})
        .then( r =>{
            console.log('r',r);
            r.forEach(p =>{
                if(p.Pricebook2Id=="01s46000004XMWhAAO"){
                    ctx.tes=p.UnitPrice;
                    console.log(this.tes);
                }else if(p.Pricebook2Id=="01se0000000sup8AAA"){
                    ctx.stpb=p.UnitPrice;
                }
            })
            columnData.push({ "ProductCode" : ctx.allProductColumns[i].fields.ProductCode.value,
                          "Name" : ctx.allProductColumns[i].fields.Name.value, 
                          "Family" : ctx.allProductColumns[i].fields.Family.value,
                          "01s46000004XMWhAAO" : ctx.tes,
                          "01se0000000sup8AAA" : ctc.stpb
                          }); 
        })
        .catch(error =>{
            ctx.message = 'Error received: code ' + error.errorCode + ', ' + 'message ' + error.body.message;
            console.log(ctx.message);
        });

Asynchronous JS may not be obvious, but there's some syntax sugar to make it easier to read. Take a look at async/await keywords. They basically make asynchronous code look synchronous. In your case that would be something like this :

@wire(getListUi, {objectApiName: PRODUCT_OBJECT,listViewApiName: '$Value'}) 
async lastView({error,data}){
    if (data) {
        var columnData = [];
        var pb1,pb2,pb3,stpb,sup,tes;
        var santosh=0;
        this.allProductColumns=data.records.records;
        for(var i=0;i<this.allProductColumns.length;i++){
            try {
                const r = await getPriceBookEntry({productId: this.allProductColumns[i].id});
            
                console.log('r',r);
                r.forEach(p =>{
                    if(p.Pricebook2Id=="01s46000004XMWhAAO"){
                        this.tes=p.UnitPrice;
                        console.log(this.tes);
                    }else if(p.Pricebook2Id=="01se0000000sup8AAA"){
                        this.stpb=p.UnitPrice;
                    }
                })
           
           } catch(error) {
                this.message = 'Error received: code ' + error.errorCode + ', ' + 'message ' + error.body.message;
                console.log(this.message);
            }
            columnData.push({ "ProductCode" : this.allProductColumns[i].fields.ProductCode.value,
                              "Name" : this.allProductColumns[i].fields.Name.value, 
                              "Family" : this.allProductColumns[i].fields.Family.value,
                              "01s46000004XMWhAAO" : this.tes,
                              "01se0000000sup8AAA" : this.stpb
                              });
        }
        this.allProductColumns=columnData;
    } else if (error) {
        this.error = error;
    } }

Also, something that I noticed, but didn't change. It seems like you're defining stpb and tes inside the function, but later on you use component level properties (this.stpb and this.tes). Not sure if this is intentional, so didn't fix it. My advice here is to either remove in-function vars or to use them instead of component-level ones.

One last thing. Just noticed that the code is not optimal. It would be better to get all promises into an array and then wait for the whole array and only after that to iterate over the list of results. That would be something like this:

const pbEntryPromises = [] ;
for(var i=0;i<this.allProductColumns.length;i++){
            
   pbEntryPromises.push(getPriceBookEntry({productId: this.allProductColumns[i].id}))
}

const pbEntries = await Promise.all(pbEntryPromises);
// iterate over pbEntries and execute logic

That would be more optimal then to wait for promises one by one. The same can be done without async/await.

Promise.all(pbEntryPromises).then( pbEntries => {
    //logic here
} 

Hope that helps.

| improve this answer | |
  • Thanks a lot. I'm just getting started in JS and Apex and starting to learn everything. Thanks for taking time and writing this detailed and lengthy answer. I can't really express how thankful I am. – Nikhil Rayala Jul 22 at 14:35

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.