Hi, Is it possible to create custom asynchronous f...
# suitescript
m
Hi, Is it possible to create custom asynchronous functions in suitescript (ie not just using the built in supported promise functions)? I’m trying to run a big search in the background while doing other stuff and then when the search.run().each() has finished doing what I want it to do, I’ll handle the results. I have a scheduled script like the one below, and I would be expecting the order of the logs to be: 1. “Start of runSearch” 2. “Meanwhile” 3. “Finishing runSearch” 4. “myResults” But instead, “Meanwhile” doesn’t log until runSearch() has finished (which I know, because in my actual script I’m also logging the time elapsed), and “Finishing runSearch” and “myResults” don’t log at all. So I’m having a few problems, one being that the promise doesn’t seem to work since it’s not logging everything, another being that the async execute function seems to wait until the promise function has finished even though I’m not using the await keyword when I call it, and the last one being that for some reason the “myResults” line doesn’t even log at all. Any thoughts?
Copy code
async function execute(scriptContext) {
    let transactionSearch = search.load('searchinternalid')
    let pendingPromise = runSearch(transactionSearch);
    log.debug('execute', 'Meanwhile');
    let myResults = await pendingPromise;
    log.debug('execute', 'myResults: ' + JSON.stringify(myResults))

}

function runSearch(transactionSearch) {
    log.debug('runSearch', 'Start of runSearch');
    let searchResults = [];
    return new Promise(resolve => {
        transactionSearch.run().each(result => {
            searchResults.push(result.id);
            return true;
        });
        log.debug('runSearch', 'Finishing runSearch');
        resolve(searchResults);
    })
}
b
the point of asynchronous functions is to do something else while waiting for the results from some other process
the Promise you wrote is trying to wait on itself
thats not gonna happen, your search results wont build themselves by waiting
you are also making the general mistake around suitescript's Promise implementation
there is no global Promise rejection handler, you are silently ignoring any error thrown
either use a catch on your Promise, or use a try catch block in your asynchronous function
m
I know I’m not handling errors here, this was a very basic example for when I’m not even getting an error-free scenario to work. I realize I am probably building the logic incorrectly, but I thought I was triggering the runSearch so it could do its thing in the background and wouldn’t check for its status until the await pendingPromise line
I did try using runPaged.promise().then at one point which worked, but then I need to write everything I want to do after the promise has been resolved in the “then” block, which doesn’t look very neat, and the Help pages even says to consider using async/await instead of promises which confuses me since I can’t make that work 😅
b
your description of meanwhile is normal, its how Promises work, your code doesnt do anything asynchronus, its immediately resolves
your missing logs are because your code throws an error and you arent catching it
m
Copy code
the Promise you wrote is trying to wait on itself
This is where I probably don’t fully understand how promises work. I’ve been spending quite some time reading up on them but I’m clearly still missing the basic understanding haha
b
same answer, asynchronous code allows your code to do something else while waiting for the result of something else
you arent waiting for anything in your code
m
I will definitely add the try/catch to log the errors then, thank you for catching that (😉). Is there a way to make the runSearch() function asynchronous? I thought creating a promise that doesn’t resolve until it’s done with the processing meant waiting for something
b
your promise runs a search and tries (and probably fails) to build an array of ids
if your processor tries to do something else, your array of ids wont get built
there is nothing to wait for
so your code doesnt wait
m
I appreciate your help a lot, thank you. I don’t want to take up too much of your time, so can I just ask if there are any changes you would suggest I make to be able to process the search results in the background while doing other stuff?
m
Should I use that to process the results in my runSearch function?
b
yes
m
I tried changing to that, which inside the runSearch function it seemed to work, because I added a line of logging before the return Promise line and that line was logged immediately, before the results were processed. But the “meanwhile” in the main function still wasn’t logged until after the runSearch was finished. So it seems like the specific ResultSet.each.promise part of runSearch() runs asynchronously, but the entire runSearch() as such doesn’t, so the main script won’t continue until it’s done. Which I guess makes sense, but is there a way to make the runSearch run asynchronously?
b
what did your attempt look like
m
I cleaned up the actual code some and ran it again and I may have misspoke, but it’s still leaving me confused. It looks like this
Copy code
async function execute(scriptContext) {
    startTime = Date.now();
    log.debug('execute', getElapsedTime() + ': START execute');

    let transactionSearch = search.create({
        type: search.Type.TRANSACTION,
        columns: ['tranid', 'entity', 'trandate', 'item', 'type']
    });
    let promise;
    try {
        promise = runSearch(transactionSearch);
    } catch (e) {
        log.error('execute', getElapsedTime() + ': catch 1 error: ' + JSON.stringify(e))
    }

    log.debug('test', getElapsedTime() + ': meanwhile.....');

    let results;
    try {
        results = await promise;
        log.debug('test', getElapsedTime() + ': results: ' + results.length);
    } catch (e) {
        log.error('execute', getElapsedTime() + ': catch 2 error: ' + JSON.stringify(e))
    }
}

function getElapsedTime() {
    return Date.now() - startTime
}

function runSearch(search) {
    log.debug('test', getElapsedTime() + ': starting to run search')
    let searchResults = [];
    return new Promise(resolve => {
        let counter = 0;
        search.run().each.promise(result => {
            counter++;
            let lineObj = {
                internalid: result.id,
                tranid: result.getValue('tranid'),
                trandate: result.getValue('trandate')
            }
            searchResults.push(lineObj);
            if (searchResults.length === 999) log.debug('test', getElapsedTime() + ': 999 results')
            return true;
        });
        log.debug('test', getElapsedTime() + ': finishing run search');
        resolve(searchResults);
    });
}
And weirdly (to me), the logging of “details” looks like this: 1. 0: START execute 2. 15: starting to run search 3. 3215: finishing run search 4. 3241: meanwhile..... 5. 3366: results: 500 6. 3480: 999 results
So the search.run().each.promise seems to run in the background, since that gets logged last, but for some reason it still takes the script about 3 seconds to reach “finishing run search”, which is almost the entirety of the time it takes for the search.run().each.promise to finish running
b
this try catch is useless
Copy code
try {
        promise = runSearch(transactionSearch);
    } catch (e) {
        log.error('execute', getElapsedTime() + ': catch 1 error: ' + JSON.stringify(e))
    }
it doesnt handle a rejected Promise
still using Promises wrong too
Copy code
return new Promise(resolve => {
        let counter = 0;
        search.run().each.promise(result => {
            counter++;
            let lineObj = {
                internalid: result.id,
                tranid: result.getValue('tranid'),
                trandate: result.getValue('trandate')
            }
            searchResults.push(lineObj);
            if (searchResults.length === 999) log.debug('test', getElapsedTime() + ': 999 results')
            return true;
        });
        log.debug('test', getElapsedTime() + ': finishing run search');
        resolve(searchResults);
    });
it looks like that function is supposed to resolve when you have all the search results
however you dont use then or await so it wont wait
in general, you are using Promises wrong if you are using the Promise constructor
its main use is to make a callback style asynchronous api use Promises
m
I feel like there’s one big puzzle piece I’m missing to fully understand the concept 😅 If I use “await”, doesn’t that tell the script to pause until the promise has been resolved? While what I want to do is for the script to not wait right away, I want it to start the “runSearch” function on the line “promise = runSearch(transactionSearch)“, but then I want it to immediately move onto the “log.debug(‘test’, getElapsedTime() + ’: meanwhile.....’);” line. Then, on the “results = await promise”, I want to wait until the search results have been processed, which I’m trying to flag with the “resolve(searchResults)” in the runSearch() function
b
its somewhat hidden when you use async and await
m
I’m not a trained programmer by any means, I’m self-taught and I’ve learned what I needed to do my job, so that’s why I’m lacking an understanding of certain areas and promises seems to be one of them, where my reading up on it hasn’t really helped
Can I make some changes to my code to make it do what I want it to, or is it not possible?
b
but the point of the Promise is that you return that Promise and attach functions to it to define what you do after that Promise resolves
after you return a Promise, you can do whatever you want afterwards
and rely on the then method to choose what to do after that Promise is done
first off fix your runSearch function
you are relying on the
finishing run search
to tell when your search is done
it actually has no relation to when your search is done
its basically guaranteed to run before your search is actually done
m
I did notice that too haha, but how do I make sure the promise doesn’t resolve until the search is done?
Is that where I use “then”?
I tried that and it looks better, but for some reason the search.run seems to stop at exactly 1500 results even though there are definitely more results in the search. The help page for ResultSet.each.promise() says it’s only supported by client scripts too, which confuses me even more haha..
b
you havent gotten very far on learning Promises if you dont know when to use the then method
its mandatory
m
Haha no, you’re right, I’ve never used them before and all the examples I’ve come across have looked similar to this one, which doesn’t include any then methods. But I’ll try to find better resources where I can learn about them from scratch
b
its basically hidden when you use async and await, but there is an implicit then on the pending Promise that encapsulates all the code that is awaiting that promise
w
I have a hard time understanding promises as well. 🙂 Basically, is there any way of running multiple query.runSuiteQL().asMappedResults() in parallel in a suitelet(serverside). Or does that function contain cpu blocking code that prevents it? All my attempts of running two queries at once using promises and async/await still takes timeToRunOneQuery*2 I have a frontend that needs a lot of data and thought it would be good to fetch multiple pages of 5000 rows in parallel in the backend suitelet. But as I can't get that to work, I'll let the frontend make parallel calls to the suitelet to see if that works.
b
@Watz you havent shared your code, but if
Copy code
query.runSuiteQL().asMappedResults()
is representative of your attempts, then you are making the same mistake as @Madelene Sköld
you need to be using the promise versions of these methods
putting synchronous code in a Promise doesnt make it asynchronous
that said, only certain SuiteScript 2.x Promise APIs actually support Promises
the rest just process sequentially
w
Yeah, I probably am. I tried to wrap
query.runSuiteQL().asMappedResults()
in a promise. But as you say, only the api's that have a promise version actually works to run asynchronously. And runSuiteQL doesn't.
Not that it helps 😄 But I did something like this in the suitescript debugger.
Copy code
require(['N/query'], (query) => {
   
    function execSuiteQLAsync(SuiteQL) {
        return new Promise((resolve, reject) => {
            let results
            try {
                results = query.runSuiteQL({
                    query: SuiteQL
                }).asMappedResults()
            } catch(e) {
              reject('error')    
            }
            resolve(results)
        })
    }

    const runSuiteQL = async function() {

        log.debug('start of async function')
        let data = await Promise.all([
            execSuiteQLAsync(`SELECT id FROM transaction WHERE id between 0 and 2000`),
            execSuiteQLAsync(`SELECT id FROM transaction WHERE id between 2000 and 4000`)
        ])        
        return data
    }  
    console.log('start')
    runSuiteQL()
        .then(data =>{
            log.debug('data',data)
        })
        .catch(e=>log.debug('error',e))
})
b
same mistake as the original, your code is mostly synchronous
its slightly more asynchronous than Madelen's since it uses a then, but you wouldnt be able to tell with the code you wrote
w
haha
I thought the
await Promise.all()
was the bit that did the parallel execution part. If we assume that execSuiteQLAsync is some async function that actually behaves as such, what is making it synchronous?
b
step through your code using the debugger
or add more logs, execSuiteQLAsync will resolve before Promise.all has a chance to wait for anything
you may also want to start looking up how javascript is single threaded, it doesn't do parallel execution
w
Ok, running it through the debugger shows that everything is synchronous. But why? I've read examples where you can do Promise.all([]) on returned promise.
Not parallel for real, no. But it can do stuff while it waits for responses, right? Which I assume the call to a search or query is.
b
none of the methods you use return promises or are asnychronous
there is no waiting
w
I thought await Promise.all([]) was the waiting bit
b
it wouldnt actually wait for anything, both of the Promises you passed it are already fulfilled
w
hm, but disregard the bit where the function execSuiteQLAsync is incorrect and replace that with a bunch of search.lookupFields.promise(options). Would those still not be executed as fast as the slowest one?
b
depends on how many you mean by a bunch
w
4?
X? Is there a limit?
b
your browser still will only make so many requests at the same time
w
Ah, but in server-script?
b
and netsuite has a finite number of database connections it will use for you
w
But the logic in the code is then sound?
What little that is left of it 🙂
b
not really, you made the same mistakes as madellene
w
😂
b
your try catch in the Promise is wrong
the use of the new Promise constructor is wrong
i can keep on repeating what i said earlier, but you probably learn how to use a Promise first, then come back to do your attempts at using the Promises in suitescript
w
Could you give a small sample on how it should look?
b
you will not get a useful education on how to use Promises from netsuite's documentation
w
Just for a simple lookupFields
b
ill consider it when i see you share something that convinces me you know how to use Promises
w
Ok, let me get back on that! 😁 The frontend path that I took instead seems to be working very nicely with 15 fetch's running in semi-parallel (limited by broweser)
message has been deleted
Fetching 70000 rows divided up into 15 pages went from 350 seconds down to 50. So 7 calls asynchronously at a time.
m
@Watz how did you make the 7 asynchronous calls, did you use Promise.all for that?
And @battk while I definitely respect your right to share/help as much or little as you want, seeing examples of code that behaves a specific way is one of the best ways for me to learn new concepts. I wouldn’t just copy and paste, but rather look carefully at each step to understand what happens, but that is much easier than reading abstract information without actual comparable examples
w
(I found out it was only 6 asynchronous calls, limited by the browser) Yes I did use Promise.all(), all of this is running in the client and is independent of Suitescript/Netsuite. The only connection is that it calls a Suitelet. Here is the jist of it, edited out some irrelevant parts of it.
m
I’m sorry, I didn’t see this reply at all. Thank you so much, I’ll take a look and see if I can draw inspiration from it that I can apply on my project! 😄