我在Node.js中有一组函数,我想按特定顺序加载。我将提供一些抽象和简化的模型代码:
function updateMyApp() {
loadDataToServer()
.then(() => useData())
.then(() => saveData())
.then(() => { console.log("updateMyApp done") })
}
function loadDataToServer() {
return new Promise( (resolve, reject) {
...preparing data and save file to cloud...
resolve()})
}
function handleDataItem(item) {
// Function that fetches data item from database and updates each data item
console.log("Name", item.name)
}
function saveData() {
// Saves the altered data to some place
}
useData有点复杂。在它中,我想,按顺序:
- console.log('Starting alterData(('(
- 从云数据源以json形式加载数据
- 遍历json文件中的每个项,并对其进行处理DataItem(项(
- 当#2完成时->console.log('alterData((done'(
- 将已解决的承诺返回到updateMyApp
- 继续使用
saveData()
,更改所有数据
我希望日志显示:
Starting useData()
Name: Adam
Name: Ben
Name: Casey
useData() done
我对此的看法如下:
function useData() {
console.log('Starting useData()')
return new Promise( function(resolve, reject) {
readFromCloudFileserver()
.then(jsonListFromCloud) => {
jsonListFromCloud.forEach((item) => {
handleDataItem(item)
}
})
.then(() => {
resolve() // I put resolve here because it is not until everything is finished above that this function is finished
console.log('useData() done')
}).catch((error) => { console.error(error.message) })
})
}
这似乎是有效的,但据我所知,这不是一个人应该做的。此外,这似乎是在这个链之外做handleDataItem
,所以日志看起来像这样:
Starting useData()
useData() done
Name: Adam
Name: Ben
Name: Casey
换句话说。当链进入下一步(.then(((时,handleDataItem
((调用似乎没有完成。换句话说,当它进入saveData()
函数时,我不能确定所有项目都已更新?
如果这不是一个很好的处理方法,那么应该如何编写这些函数呢?我如何正确地链接函数,以确保一切都按正确的顺序进行(以及使日志事件按顺序显示(?
编辑:根据请求,这是handleDataItem的抽象化。
function handleDataItem(data) {
return new Promise( async function (resolve) {
data['member'] = true
if (data['twitter']) {
const cleanedUsername = twitterApi.cleanUsername(data['twitter']).toLowerCase()
if (!data['twitter_numeric']) {
var twitterId = await twitterApi.getTwitterIdFromUsername(cleanedUsername)
if (twitterId) {
data['twitter_numeric'] = twitterId
}
}
if (data['twitter_numeric']) {
if (data['twitter_protected'] != undefined) {
var twitterInfo = await twitterApi.getTwitterGeneralInfoToDb(data['twitter_numeric'])
data['twitter_description'] = twitterInfo.description
data['twitter_protected'] = twitterInfo.protected
data['twitter_profile_pic'] = twitterInfo.profile_image_url.replace("_normal", '_bigger')
data['twitter_status'] = 2
console.log("Tweeter: ", data)
}
} else {
data['twitter_status'] = 1
}
}
resolve(data)
}).then( (data) => {
db.collection('people').doc(data.marker).set(data)
db.collection('people').doc(data.marker).collection('positions').doc(data['report_at']).set(
{
"lat":data['lat'],
"lon":data['lon'],
}
)
}).catch( (error) => { console.log(error) })
}
twitterAPI函数名为:
cleanUsername: function (givenUsername) {
return givenUsername.split('/').pop().replace('@', '').replace('#', '').split(" ").join("").split("?")[0].trim().toLowerCase()
},
getTwitterGeneralInfoToDb: async function (twitter_id) {
var endpointURL = "https://api.twitter.com/2/users/" + twitter_id
var params = {
"user.fields": "name,description,profile_image_url,protected"
}
// this is the HTTP header that adds bearer token authentication
return new Promise( (resolve,reject) => {
needle('get', endpointURL, params, {
headers: {
"User-Agent": "v2UserLookupJS",
"authorization": `Bearer ${TWITTER_TOKEN}`
}
}).then( (res) => {
console.log("result.body", res.body);
if (res.body['errors']) {
if (res.body['errors'][0]['title'] == undefined) {
reject("Twitter API returns undefined error for :'", cleanUsername, "'")
} else {
reject("Twitter API returns error:", res.body['errors'][0]['title'], res.body['errors'][0]['detail'])
}
} else {
resolve(res.body.data)
}
}).catch( (error) => { console.error(error.message) })
})
},
// Get unique id from Twitter user
// Twitter API
getTwitterIdFromUsername: async function (cleanUsername) {
const endpointURL = "https://api.twitter.com/2/users/by?usernames="
const params = {
usernames: cleanUsername, // Edit usernames to look up
}
// this is the HTTP header that adds bearer token authentication
const res = await needle('get', endpointURL, params, {
headers: {
"User-Agent": "v2UserLookupJS",
"authorization": `Bearer ${TWITTER_TOKEN}`
}
})
if (res.body['errors']) {
if (res.body['errors'][0]) {
if (res.body['errors'][0]['title'] == undefined) {
console.error("Twitter API returns undefined error for :'", cleanUsername, "'")
} else {
console.error("Twitter API returns error:", res.body['errors'][0]['title'], res.body['errors'][0]['detail'])
}
} else {
console.error("Twitter API special error:", res.body)
}
} else {
if (res.body['data']) {
return res.body['data'][0].id
} else {
//console.log("??? Could not return ID, despite no error. See: ", res.body)
}
}
},
您有3个选项来处理循环中异步方法的主要问题。
-
使用
map
并返回promise而不是forEach
。然后对返回的promise使用Promise.all
,等待它们全部完成。 -
将
for/of
循环与async/await结合使用。 -
使用
for await
循环。
听起来handleDataItem()
的实现和它返回的承诺有问题。为了帮助您,我们需要查看该函数的代码。
您还需要清理useData()
,以便它正确地返回一个传播完成和错误的promise。
而且,如果handleDataItem()
返回了一个准确的promise,那么您也需要在这里的循环中更改这样做的方式。
更改:
function useData() {
console.log('Starting useData()')
return new Promise( function(resolve, reject) {
readFromCloudFileserver()
.then(jsonListFromCloud) => {
jsonListFromCloud.forEach((item) => {
handleDataItem(item)
}
})
.then(() => {
resolve() // I put resolve here because it is not until everything is finished above that this function is finished
console.log('useData() done')
}).catch((error) => { console.error(error.message) })
})
}
到此:
async function useData() {
try {
console.log('Starting useData()')
const jsonListFromCloud = await readFromCloudFileserver();
for (let item of jsonListFromCloud) {
await handleDataItem(item);
}
console.log('useData() done');
} catch (error) {
// log error and rethrow so caller gets the error
console.error(error.message)
throw error;
}
}
这里的结构变化是:
- 切换为使用
async/await
更容易地处理循环中的异步项 - 删除将
new Promise()
包裹在现有promise周围的promise反模式-无需这样做,并且您没有捕获或传播来自readFromCloudFileServer()
的拒绝,这是使用该反模式时常见的错误 - 在记录错误后,在捕获中重新抛出错误,以便将错误传播回调用方