asynchronous/ Callstack混淆

好吧,我很确定我知道问题是什么,但我不能为了我的生活找出解决办法。

下面代码的工作方式是前端将两个字发回服务器,发生一些消毒,并将string分解成一个数组。 然后迭代该数组,为每个单词向Wordnik API提供asynchronous请求。 返回给客户端的结果数据结构是一个带有{word1: [...synonyms], word2: [...synonyms]}

用两个字,这个工作正是我想要4次5次。 第五次,第二个单词的同义词被应用到第一个单词,第二个单词没有数据。 显然,发送更多的话,数据混淆更频繁发生。

所以,我很确定这是一个调用堆栈问题,但我不知道如何解决它。 我一直在想,如果我把wordnikClient包在setTimeout(…,0)中; 这是朝着正确的方向迈出的一步,但感觉就像我误用了这个模式。 那里有什么智慧的话?

编辑: https : //github.com/ColinTheRobot/tweetsmithy-node/blob/master/server.js这是以前的版本,它具有相同的asynchronous问题。 我最初是用Promisedevise的,但是在过去的几天里,我意识到它没有做任何事情/我也可能误用了它,所以现在就把它拿出来。

 app.get('/get-synonyms', (req, res) => { var tweetWords = sanitizeTweet(req.query.data); getDefs(tweetWords, res); }); var getDefs = function(tweetWords, res) { var i = 0; var serialized = {}; tweetWords.forEach((word) => { wordnikClient(word, (body) => { var wordToFind = tweetWords[i]; var shortenedWords = []; i++; if (body[0]) { shortenedWords = _.filter(body, (syn) => { return syn.length < wordToFind.length; }); serialized[wordToFind] = shortenedWords; } if (tweetWords.length == i) { res.send(serialized); } }); }); } var sanitizeTweet = function(tweet) { var downcasedString = tweet.toLowerCase(); var punctuationless = downcasedString.replace(/[.,-\/#!$%\^&\*;:{}=\-_`~()]/g,""); var finalString = punctuationless.replace(/\s{2,}/g," "); return finalString.split(' '); } var wordnikClient = function(word, callback) { var url = `http://api.wordnik.com:80/v4/word.json/${word}/relatedWords?useCanonical=false&relationshipTypes=synonym&limitPerRelationshipType=10&api_key=${process.env.WORDNIK_API_KEY}` console.log('calling client'); request(url, (err, response, body) => { if (!err && response.statusCode == 200 && response.body != '[]') { callback(JSON.parse(body)[0].words); } else if (!err && response.statusCode == 200 && response.body == '[]') { callback([false]); } }); } 

是的,发生什么事是你的第二个asynchronous调用是先完成,因为fo

 if (tweetWords.length == i) { res.send(serialized); } }); 

正在返回给客户。 一种替代方法是使用https://github.com/caolan/async来协调您的asynchronous调用,但我build议您&#x5C06;wordnikClient转换为承诺,然后使用Promise.all来控制res.send

 var wordnikClient = function(word) { var url = `http://api.wordnik.com:80/v4/word.json/${word}/relatedWords?useCanonical=false&relationshipTypes=synonym&limitPerRelationshipType=10&api_key=${process.env.WORDNIK_API_KEY}` console.log('calling client'); return new Promise( (resolve, reject) => { request(url, (err, response, body) => { if (!err && response.statusCode == 200 && response.body != '[]') { resolve(JSON.parse(body)[0].words); } else if (!err && response.statusCode == 200 && response.body == '[]') { reject([false]); } }); }); 

 Promise.all(tweetWords.map((word) => wordnikClient(word))) .then(serialized => res.send(serialized)) .catch(err => res.status(500).send(err)) 

我可能已经失去了一些function,但你可以重新添加

asynchronouscallback在getDefs做什么不清楚。 ivariables计数答复的顺序,所以我不明白为什么要使用它来索引tweetWords 。 我build议你只用word来代替。 使用Promises可以做出更清晰的解决scheme:

 function getDefs(tweetWords, res) { var serialized = {}; Promise.all(tweetWords.map(word => { return wordnikClientAsync(word).then(body => { if (body[0]) { serialized[word] = _.filter(body, syn => syn.length < word.length); } }); })).then(() => { res.send(serialized); }, () => { res.send("Error"); }); function wordnikClientAsync(word) { return new Promise(resolve => wordnikClient(word, resolve)); } } 

更改tweetWords[i]; word因为variables在callback之外,迭代可能不会运行。