如何重构包含Promise的async函数



使用返回Promise的async函数:

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
},
});
}
});
});
}

我不确定它是否真的需要包含new Promise,因为它已经被声明为async function。在这种情况下是强制性的吗?

因为当这部分被移除,而在最后取代return resolve({...})时,只有return {...},这似乎没有解决。

下面是修改后的代码,new Promise和不同的返回:

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
},
};
}
});
}

这个方法在其他地方被这样使用:

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

,然后检查:

if (myInfo.success) { ... }

对于第一种写法,它工作得很好,对于第二种写法,myInfoundefined,它抛出一个错误,无法读取未定义的success

实现中缺少了什么吗?

对于第二个版本,我可以看到你实际上没有从doSomething返回任何东西,所以我认为你应该这样做:

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;
}
一般来说,你不需要在async函数中显式返回Promise,因为默认情况下,返回值将被包装在Promise中,不管它是什么。你只需要返回一些东西

Room.aggregatePaginat是用延续传递的方式编写的,它不能很好地与承诺接口。通用的promisify函数可用于将任何延续传递样式的函数转换为基于承诺的函数。如果您不想自己编写这个函数,Node提供了util.promisify-

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

现在很容易重构你的doSomething。注意:使用Promise.all意味着所有rooms数据都是并行处理的,而不是串行处理的-

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: ...
}
}

你也应该避免像{ success: true }这样的东西,因为一个已解决的承诺本质上是"成功的"。

注意return出现在throw之后。在这种情况下,return是不可访问的,所以它不会做你认为它在做的事情。

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

再说一次,{ success: false }违背了Promise模式。如果您想从拒绝中恢复,并使用空{ rooms: [] }列表恢复,请这样做-

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

更好的是,您可以在doSomething中使用try..catch,并在发生错误时返回相应的空响应。这可以防止错误冒泡并迫使用户处理它-

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))

最新更新