如何确保Node.js中的这些函数链按顺序执行(使用promise)



我在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有点复杂。在它中,我想,按顺序

  1. console.log('Starting alterData(('(
  2. 从云数据源以json形式加载数据
  3. 遍历json文件中的每个项,并对其进行处理DataItem(项(
  4. 当#2完成时->console.log('alterData((done'(
  5. 将已解决的承诺返回到updateMyApp
  6. 继续使用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个选项来处理循环中异步方法的主要问题。

  1. 使用map并返回promise而不是forEach。然后对返回的promise使用Promise.all,等待它们全部完成。

  2. for/of循环与async/await结合使用。

  3. 使用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;
}
}

这里的结构变化是:

  1. 切换为使用async/await更容易地处理循环中的异步项
  2. 删除将new Promise()包裹在现有promise周围的promise反模式-无需这样做,并且您没有捕获或传播来自readFromCloudFileServer()的拒绝,这是使用该反模式时常见的错误
  3. 在记录错误后,在捕获中重新抛出错误,以便将错误传播回调用方

最新更新