简体   繁体   中英

How to refactor an async function with Promise inside of it

Having this async function that returns a Promise:

async function doSomething(userId) {
  return new Promise(async (resolve, reject) => {
    const query = 'my-query';

    const roomAggregate = Room.aggregate(query).allowDiskUse(true);
    Room.aggregatePaginate(roomAggregate, async function (err, data) {
      if (err || !data) {
        throw err;
        return {
          success: false,
          rooms: [],
        };
      } else {
        const rooms = [];
        for (let room of data.docs) {
          const roomId = room._id;
          room = await computeAThing(room, {
            loadWriteUps: false,
            loadCreators: false,
          });
          room.userCompleted = await computeBThing(userId, roomId);
          rooms.push(room);
        }
        return resolve({
          rooms,
          success: true,
          paginator: {
            // some data related to pagination
          },
        });
      }
    });
  });
}

I'm not sure if it really needs to contain new Promise inside as it is already declared as async function . Is it mandatory in this case?

Because when that part was removed and at the end instead of return resolve({...}) it is only return {...} it seems to not settle.

Here is the modified code with new Promise and different return:

async function doSomething(userId) {
  const query = 'my-query';

  const roomAggregate = Room.aggregate(query).allowDiskUse(true);
  Room.aggregatePaginate(roomAggregate, async function (err, data) {
    if (err || !data) {
      throw err;
      return {
        success: false,
        rooms: [],
      };
    } else {
      const rooms = [];
      for (let room of data.docs) {
        const roomId = room._id;
        room = await computeAThing(room, {
          loadWriteUps: false,
          loadCreators: false,
        });
        room.userCompleted = await computeBThing(userId, roomId);
        rooms.push(room);
      }
      return {
        rooms,
        success: true,
        paginator: {
          // some data related to pagination
        },
      };
    }
  });
}

This method is used somewhere else in this way:

 const myInfo = await someObj.doSomething('myUserId');

and then checked:

if (myInfo.success) { ... }

For the first way of writing the function it works fine, for the second one myInfo is undefined and it throws and error that cannot read success of undefined.

Is something missing from the implementation?

For the second version I can see that you actually not returning anything from doSomething so I think you should do the below:

  async function doSomething(userId) {
  const query = 'my-query';

  const roomAggregate = Room.aggregate(query).allowDiskUse(true);
  const obj = Room.aggregatePaginate(roomAggregate, async function (err, data) {
    if (err || !data) {
      throw err;
      return {
        success: false,
        rooms: [],
      };
    } else {
      const rooms = [];
      for (let room of data.docs) {
        const roomId = room._id;
        room = await computeAThing(room, {
          loadWriteUps: false,
          loadCreators: false,
        });
        room.userCompleted = await computeBThing(userId, roomId);
        rooms.push(room);
      }
      return {
        rooms,
        success: true,
        paginator: {
          // some data related to pagination
        },
      };
    }
  });
  return obj;
}

In general, you don't need to explicitly return a Promise in an async function as by default the returned value will be wrapped in a Promise, whatever it is. You just need to return something

Room.aggregatePaginat is written in coninuation-passing style, which does not interface well with promises. A generic promisify function can be used to convert any continuation-passing style function into a promise-based one. If you don't wish to write this function yourself, it is provided by Node as util.promisify -

const promisify = f => (...args) =>
  new Promise((resolve, reject) =>
    f(...args, (err, data) => err ? reject(err) : resolve(data))
  )

Now it's easy to refactor your doSomething . Note use of Promise.all means all rooms data is processed in parallel rather than in series -

async function doSomething(userId) {
  const query = 'my-query'
  const roomAggregate = Room.aggregate(query).allowDiskUse(true)
  const {docs:rooms} = await promisify(Room.aggregatePaginate)(roomAggregate)
  return {
    rooms:
      await Promise.all(rooms.map(async room => ({
        ...await computeAThing(room, {loadWriteUps: false, loadCreators: false}),
        userCompleted: await computeBThing(userId, room._id)
      }))),
    success: true,
    paginator: ...
  }
}

Also you should avoid things like { success: true } because a resolved promise is inherently "successful". A rejected one is not.

And watch out for return occurring after a throw . In this case the return is unreachable so it doesn't do what you think it's doing.

if (err || !data) {
  throw err;
  return {            // unreachable 
    success: false,   // these four lines
    rooms: [],        // are completely ignored
  };                  // by javascript runtime
}

Again, { success: false } goes against the Promise pattern anyway. If you want to recover from a rejection and recover with an empty list of { rooms: [] } , do it like this instead -

doSomething(user.id)
  .catch(err => ({ rooms: [] }))
  .then(res => console.log(res.rooms))

Better yet, you can try..catch inside doSomething and return the appropriate empty response in the event of an error. This prevents the error from bubbling up and forcing the user to handle it -

async function doSomething(userId) {
  try {                           // try
    const query = 'my-query'
    const roomAggregate = Room.aggregate(query).allowDiskUse(true)
    const {docs:rooms} = await promisify(Room.aggregatePaginate)(roomAggregate)
    return {
      rooms:
        await Promise.all(rooms.map(async room => ({
          ...await computeAThing(room, {loadWriteUps: false, loadCreators: false}),
          userCompleted: await computeBThing(userId, room._id)
        }))),
      paginator: ...
    }
  }
  catch (err) {             // catch
    return { rooms: [] }
  }
}
doSomething(user.id)
  .then(res => console.log(res.rooms))

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