Async Operations!

I was working with a colleague and we were trying to achieve something by chaining multiple requests as a pre-step before the actual request happened.

Use case:
We wanted to call 3-4 specific endpoints in a specific order and gather all the required data before the actual request was sent out.

One way was to do it by creating multiple requests and using the collection runner. But the other way was to make use of pm.sendRequest, however, there’s no guarantee in which order multiple of pm.sendRequest would execute during the execution phase of the script.

So, I ended up creating 2 operations of the famous async library (probably will work on adding more) in the most naive way as possible, but it works so… ¯\(ツ)

This is just an example to showcase what’s do-able using scripts, and how a lot of things can be achieved with chaining everything in just one request.

Reference:
See the pre-request scripts of each request in the following collection and fork the collection if you want to try it out:
https://www.postman.com/postman/workspace/postman-answers/collection/3407886-220af2f6-63f2-4d84-903d-99e6e296a8c8?ctx=documentation

Example code snippet:

// Expected output in console: 3, 1, 2
asyncSeries([
    (cb) => pm.sendRequest('https://postman-echo.com/delay/3', (err, res) => {
        console.log(res.json().delay);
        cb(err, res);
    }),
    (cb) => pm.sendRequest('https://postman-echo.com/delay/1', (err, res) => {
        console.log(res.json().delay);
        cb(err, res);
    }),
    (cb) => pm.sendRequest('https://postman-echo.com/delay/2', (err, res) => {
        console.log(res.json().delay);
        cb(err, res);
    })
], (err, res) => {
    console.log('Series operations resolved', err, res);
});

Screenshot:

8 Likes

This is awesome @sivcan! Thanks for putting this in a collection that I can play around on my own.

And that documentation in the Pre-request tabs?! :heart_eyes_cat: :heart_eyes_cat: :heart_eyes_cat:

3 Likes

@sivcan It looks great! just had a question is there any drawbacks for below approaches when compared to this

Approach 1

pm.sendRequest("https://postman-echo.com/delay/6", (error, res) => {

    console.log(res)

    pm.sendRequest("https://postman-echo.com/delay/1", (error, res) => {

        console.log(res)

        pm.sendRequest("https://postman-echo.com/delay/2", (error, res) => {

            console.log(res)

        })

    })

})

Approach 2:


pm.environment.set("final", [])

let promisify = function (req) {

    return new Promise((resolve, reject) => {

        pm.sendRequest(req, (error, res) => {

            setTimeout(resolve(res), 1);

        })

    });

};

async function test() {

    await promisify("https://postman-echo.com/delay/3")

    await promisify("https://postman-echo.com/delay/1")

    await promisify("https://postman-echo.com/delay/2")

}

test()

//set time out to the max response time of the 3 requests

// here 3000 or 3 sec is the highest so we are keeping 5000

setTimeout(()=>{},5000)
4 Likes

@sean.keegan Thanks, I am glad it’s useful!

@praveendvd Thanks for sharing your thoughts. My thoughts over the approaches you shared:
Approach 1: Callback hell! Yes, that’s what promises help you avoid, but currently the Postman script execution doesn’t support promises. So for lot of operations, this approach becomes a really big problem.

Approach 2: You’re kind of simulating a race situation here since again promises don’t work as you’d expect them to in the postman eco-system. I’ve personally found async library’s ways of working with callbacks pretty awesome and that’s why I prefer going with the approach that we used in the requests.
Also, since you’ve tried to simulate a race condition here, I added it as a new request in the collection itself: async.race !

4 Likes

@sivcan :heart_eyes: :heart_eyes: Thank you so much. That’s really cool , i simplified that code by calling the cdn equalent of the async library . Really thanks for this learned something new today


// calling async library cdn function

pm.sendRequest("https://cdnjs.cloudflare.com/ajax/libs/async/3.2.0/async.js", (error, res) => {

    //initializing async

    (new Function(res.text()))();

    //create a call back function that will act as the iteratee

    //we should call callback() for the async library to work

    let requestCallbackWrapper = function (req, callback) {

        pm.sendRequest(req, (error, res) => {

            console.log(res.json())

            callback()

        })

    };

    //pass the urls as array in order

    //each item and a callback will be passed to iteratee we created

    async.eachSeries(["https://postman-echo.com/delay/3", "https://postman-echo.com/delay/5", "https://postman-echo.com/delay/1"], requestCallbackWrapper, function (err) {

        console.log(err);

    });

});
1 Like

@sivcan The above code works but if i try to use the same code again it will throw error cannot call eachSerise of undefined. Could you help me out i am not understanding whats wrong .

steps to reproduce:

  1. Add the above script to both pre-request and test script
  2. Now send the request

image

1 Like

using min build works:


pm.sendRequest("https://cdnjs.cloudflare.com/ajax/libs/async/3.2.0/async.min.js", (error, res) => {

    //initializing async

    eval(res.text());

    //create a call back function that will act as the iteratee

    //we should call callback() for the async library to work

    let requestCallbackWrapper = function (req, callback) {

        pm.sendRequest(req, (error, res) => {

            console.log(res.json())

            callback()

        })

    };

    //pass the urls as array in order

    //each item and a callback will be passed to iteratee we created

    this.async.eachSeries(["https://postman-echo.com/delay/3", "https://postman-echo.com/delay/5", "https://postman-echo.com/delay/1"], requestCallbackWrapper, function (err) {

        console.log(err);

    });

});


1 Like

but currently the Postman script execution doesn’t support promises

Really? I use it to promisify pm.sendRequest() and with async/await:

Collection pre-request script:

// export common utility functions
pm.globals.set('util', String(() => ({

    // use the open interval hack to wait until async
    // operations are done before sending the request
    // in a pre-request script
    waitUntilDone(promise) {
        const wait = setInterval(() => {}, 300000);
        promise.finally(() => clearInterval(wait));
    },

    // promisified pm.sendRequest()
    sendRequest(req) {
        return new Promise(
            (resolve, reject) => pm.sendRequest(req, (err, res) => {
                if (!err && res.code / 100 < 4) return resolve(res);
                let message = `Request "${req.url||req}" failed (${res.code})`;
                if (err?.message) message += `: ${err.message}`;
                reject({message});
            }));
    },

    // load external library modules in order,
    // then return this[thisProp] or just this
    async loadModules(urls, thisProp=undefined) {
        const thisKeys = Object.keys(this);
        (await Promise.all(urls.map(this.sendRequest)))
            .forEach(res => eval(res.text()));

        const thisObj = _.omit(this, thisKeys);
        //console.log('KEYS: this', Object.keys(thisObj));
        return !thisProp && thisObj || thisObj[thisProp];
    },
})));

Main request pre-request script:

// import common utility functions
const util = eval(pm.globals.get('util'))();

async function echoDelay(secs) {
    const url = `https://postman-echo.com/delay/${secs}`;
    return (await util.sendRequest(url)).json().delay;
}

async function asyncSteps() {
    console.log(await echoDelay(3));
    console.log(await echoDelay(1));
    console.log(await echoDelay(2));

    // perform some other async file loading
    const {moment} = await util.loadModules([
        'https://momentjs.com/downloads/moment.min.js',
        'https://momentjs.com/downloads/moment-timezone-with-data.min.js',
    ]);
    const tsToDate = ts => {
        const date = new Date(ts);
        let   str  = moment(date).format('L hh:mm:ss');
        const tz   = moment.tz(date, moment.tz.guess());
        const dur  = moment.duration(date - new Date());
        return `${str} ${tz.zoneAbbr()} (${dur.humanize(true)})`;
    };
    console.log(tsToDate(1615715966000));
}
util.waitUntilDone(asyncSteps().catch(console.error));
1 Like

It doesn’t in contrast with the native behaviour of promises, you cannot halt script execution where the script will wait till all your promises resolve/reject, the script execution only halts for setTimeout/Interval(s) or pm.sendRequest function calls, and this also is one way to hack around that you shared: