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.