简体   繁体   中英

Asynchronous looping with promises?

I have the following code which registers a series of socketio namespaces. PArts of the logic depend on database calls (via sequelize), hence I need to use promises. I want the complete promise to resolve when all the constructor called logic is complete. My issue though is that the complete promise resolves before the emitInitialPackage() function has resolved.

export class demoClass {
    public complete: Promise<boolean>;
    server: any;

    constructor(io: any) {
        this.server = io;
        this.complete = Promise.resolve(db.Line.findAll()).then(lines => {
                // do some mapping to generate routes and cells
                this.registerEndPoints(routes, [], cells);
            }).catch(err => console.log(err))
    }



registerEndPoints(routes: Array<string>, nsps: Array<any>, cells: Array<string>) {
        for (let i = 0; i < routes.length; i++) {
            nsps.push(this.server.of('/api/testNamespace/' + routes[i]));
            let that = this;
            const nsp = nsps[i];
            nsp.on('connection', function (socket) {
                that.emitInitialPackage(nsps[i], routes[i], cells[i]);
            });
        }
    }

    emitInitialPackage(nsp: any, name: string, cell: any) {
        return db.Line.find({/* Some sequelize params*/}).then(results => {
            nsp.emit('value', results);
        }).catch(err => console.log(err));
    }
}

How can I ensure emitInitialPackage has completed before complete resolves?

To wait for completion of all operations in registerEndPoints , this method should return Promise that can be chained after db.Line.findAll() operation:

export class demoClass {
    public complete: Promise<boolean>;
    server: any;

    constructor(io: any) {
        this.server = io;
        this.complete = Promise.resolve(db.Line.findAll()).then(lines => {
            // return promise created by registerEndPoints method
            return this.registerEndPoints(routes, [], cells);
        }).catch(err => console.log(err))
    }   

    registerEndPoints(routes: Array<string>, nsps: Array<any>, cells: Array<string>) {
        const endPointPromises = routes.map((route, index) => {
          // for each endpoint create Promise that gets resolved 
          // only when all work for endpoint is done
          return new Promise((resolve) => {
              nsps.push(this.server.of('/api/testNamespace/' + route));
              const nsp = nsps[index];
              nsp.on('connection', (socket) => {
                // resolve promise when emitInitialPackage did its part of the work
                this.emitInitialPackage(nsps[index], route, cells[index]).then(resolve);
              });          
          });
        });

        return Promise.all(endPointPromises);
    }

    emitInitialPackage(nsp: any, name: string, cell: any) {
        return db.Line.find({/* Some sequelize params*/}).then(results => {
            nsp.emit('value', results);
        }).catch(err => console.log(err));
    }
}

There are several issues to solve:

  • Return the promise returned by this.registerEndPoints , so this.complete will only resolve when that promise resolves;
  • The [] argument passed for the nsps parameter serves no purpose as argument, so you might as well skip that parameter all together;
  • The .on('connection', ...) function should be wrapped to return a promise;
  • The for loop should create these promises, and then pass them to Promise.all for the final result. map can be used for this;
  • It is not a nice structure where you have three arrays ( routes , cells , and nsps ) that have related data at equal indexes. A better structure is where you have one array of objects, where each object has three properties: ( route , cell , and nsp );
  • bluebird is promises/A+ compliant, so there should be no need to convert a bluebird promise to a native JS promise.

Here is some untested code:

constructor(io: any) {
    this.server = io;
    // *** bluebird is promises/A+ compliant, no need to convert it:
    this.complete = db.Line.findAll().then(lines => {
        // do some mapping to generate routes and cells
        // *** return the promise!
        // *** removed [] argument: not needed
        return this.registerEndPoints(routes, cells);
    }).catch(err => console.log(err))
}

// *** Remove the nsps parameter
registerEndPoints(routes: Array<string>, cells: Array<string>) {
    // *** Create a promise-version of the `.on('connection', ...)` method
    function nspConnect(nsp) {
        return new Promise( resolve => nsp.on('connection', resolve) );
    }
    let that = this;
    // *** Combine each route, cell, and nsp in one object, and put in array:
    const data = routes.map( (route, i) => ({
        nsp: that.server.of('/api/testNamespace/' + route),
        route,
        cell: cells[i]
    }) );
    // *** Map the array of objects to promises
    const proms = data.map( ({nsp, route, cell}) =>
        nspConnect(nsp).then(that.emitInitialPackage.bind(that, nsp, route, cell)) );
    // *** Return a promise that resolves when all these have resolved
    return Promise.all(proms); 
}

To have some debugging possibilities, you could expand the nspConnect function to:

function nspConnect(nsp) {
    return new Promise( resolve => { 
        console.log('creating promise'); 
        return nsp.on('connection', socket => { 
            console.log('resolving'); 
            resolve();
        }); 
    });
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM