Home > Software design >  NestJS: Create promises from an array of arrays and run them in parallel, producing a formatted outp
NestJS: Create promises from an array of arrays and run them in parallel, producing a formatted outp

Time:07-30

I am having trouble understanding how to efficiently use promises in NestJS. There are similar questions on SO but none for this use case

I have a data structure that is an array of arrays of orders. Id like to iterate through this, call an API for each order, and populate one large array with the results.

My data structure (simplified):

let fullSalesOrderTrackingDetails = [
    [
        {carrier: 'ups', trackingNumber: '1zzzz'},
        {carrier: 'ups', trackingNumber: '2zzz'},
        {carrier: 'ups', trackingNumber: '3xxx'},
    ],
    [
        {carrier: 'fedex', trackingNumber: '1zzzz'},
        {carrier: 'fedex', trackingNumber: '2zzz'},
        {carrier: 'fedex', trackingNumber: '3xxx'},
    ]
]

I would like my result to look like this:

[
    {
        shipmentNumber: 1,
        trackingDetails: [
            // a result (Object) from the trackSalesOrder API
            // a result (Object) from the trackSalesOrder API
            // a result (Object) from the trackSalesOrder API
        ]
    },
    {
        shipmentNumber: 2,
        trackingDetails: [
            // a result (Object) from the trackSalesOrder API
            // a result (Object) from the trackSalesOrder API
            // a result (Object) from the trackSalesOrder API
        ]
    }
]

What I have so far is this:

let result = [];
await Promise.all(fullSalesOrderTrackingDetails.map(async shipment => {
    return Promise.all(shipment.map(async trackingInfo => {
        let carrier = trackingInfo.carrier;
        let trackingNumber = trackingInfo.trackingNumber;

        let trackingDetails = await this.trackingService.trackSalesOrder(carrier, trackingNumber);

        result.push(trackingDetails);
    }));
}));

Note that this does not include the logic to add the shipmentNumber index. I can also tell its not close to producing the result I'd like (nesting the results in a single trackingDetails array). I need to figure out the promise logic before attempting that.

Currently, the output is like this:

[
    {
        "_isScalar": false,
        "source": {
            "_isScalar": false
        },
        "operator": {}
    },
    {
        "_isScalar": false,
        "source": {
            "_isScalar": false
        },
        "operator": {}
    },
    {
        "_isScalar": false,
        "source": {
            "_isScalar": false
        },
        "operator": {}
    },
    {
        "_isScalar": false,
        "source": {
            "_isScalar": false
        },
        "operator": {}
    },
    {
        "_isScalar": false,
        "source": {
            "_isScalar": false
        },
        "operator": {}
    }
]

So, what's the proper way to iterate through this structure, get each shipment array, track the packages, and populate an array of objects (each containing the tracking details array)?

Thanks!

EDIT: my trackOrder function:

async trackSalesOrder(carrier: string, trackingNumber: string) {
        return this.httpService.get(process.env.SHIPENGINE_API_URL   `/v1/tracking?carrier_code=${carrier}&tracking_number=${trackingNumber}`, {headers: this.shipEngineHeaders}).pipe(map(response => response.data));
    }

CodePudding user response:

You are drilling into the arrays two levels, and then append all results to a single array. This effectively "flattens" the array. You need to process each item in the outer array wholly as an a single unit. That processing may also await promises, there's nothing wrong with that.

So in the promise for each order, just await all the tracking calls for that order, then return the assembled object.

For example:

doStuff() {
    return Promise.all(fullSalesOrderTrackingDetails.map(async (shipment, shipmentIndex) => {
        const items = await Promise.all(shipment.map(trackingInfo => {
            return this.trackingService.trackSalesOrder(
                trackingInfo.carrier,
                trackingInfo.trackingNumber
            )
        }))

        return {
            shipmentNumber: shipmentIndex   1,
            trackingDetails: items
        }
    }))
}

Which has a return type of:

Promise<{
    shipmentNumber: number;
    trackingDetails: {
        carrier: string;
        trackingNumber: string;
    }[];
}[]>

See working example in this Typescript playground


This is a full example that produces your desired output:

type FullOrder = { carrier: string, trackingNumber: string }[][]

class FooService {
    // Mock the tracking service.
    trackingService = {
        trackSalesOrder(carrier: string, trackingNumber: string) {
            return Promise.resolve({ carrier, trackingNumber, status: 'todo' })
        }
    }

    doStuff(fullSalesOrderTrackingDetails: FullOrder) {
        return Promise.all(fullSalesOrderTrackingDetails.map(async (shipment, shipmentIndex) => {
            const items = await Promise.all(shipment.map(trackingInfo => {
                return this.trackingService.trackSalesOrder(
                    trackingInfo.carrier,
                    trackingInfo.trackingNumber
                )
            }))

            return {
                shipmentNumber: shipmentIndex   1,
                trackingDetails: items
            }
        }))
    }
}

async function go() {
    const foo = new FooService()
    const result = await foo.doStuff([
        [
            {carrier: 'ups', trackingNumber: '1zzzz'},
            {carrier: 'ups', trackingNumber: '2zzz'},
            {carrier: 'ups', trackingNumber: '3xxx'},
        ],
        [
            {carrier: 'fedex', trackingNumber: '1zzzz'},
            {carrier: 'fedex', trackingNumber: '2zzz'},
            {carrier: 'fedex', trackingNumber: '3xxx'},
        ]
    ])
    console.log(result)
}

go()

That code will log out:

[{
  "shipmentNumber": 1,
  "trackingDetails": [
    {
      "carrier": "ups",
      "trackingNumber": "1zzzz",
      "status": "todo"
    },
    {
      "carrier": "ups",
      "trackingNumber": "2zzz",
      "status": "todo"
    },
    {
      "carrier": "ups",
      "trackingNumber": "3xxx",
      "status": "todo"
    }
  ]
}, {
  "shipmentNumber": 2,
  "trackingDetails": [
    {
      "carrier": "fedex",
      "trackingNumber": "1zzzz",
      "status": "todo"
    },
    {
      "carrier": "fedex",
      "trackingNumber": "2zzz",
      "status": "todo"
    },
    {
      "carrier": "fedex",
      "trackingNumber": "3xxx",
      "status": "todo"
    }
  ]
}] 
  • Related