I’m making some requests to the Twitter API, and in order to retrieve tweets I need to perform some recursive method calls as each request will only return a maximum of 100 tweets.
So the process is pretty simple.
- Call the function and await it
- Perform a http request, await that
- If the metadata of the response contains a
next_token
, cache the results and perform another http request using the next token. - Repeat until the
next_token
is undefined, at which point resolve the promise with the list of all tweets.
However this isn’t working as expected, the recursive http works, however when the else block of the recursive function satisfied and the promise is resolved, nothing happens. Execution doesn’t go back to the first function. Everything just seems to spin and do nothing. I’ve added in breakpoints on every line but nothing causes a breakpoint to trigger either.
Where am I going wrong here?
public async getTweetList(ticker: string): Promise<string[]>{
let tweets: string[] = [];
tweets = await this.getAllTweetsRecursively(ticker, null, tweets);
return tweets;
}
public async getAllTweetsRecursively(ticker: string, nextToken: string, tweetList: string[]): Promise<string[]>{
return new Promise(async (resolve, reject) => {
let query = `?query=(${ticker})`
query += this.extraQuery;
if(nextToken){
query += this.nextTokenQuery + nextToken
}
let res = await axios.default.get(this.url + query, {
headers: this.headers
})
let newNextToken = res.data.meta.next_token;
if(res.data.data.length > 0 && newNextToken){
res.data.data.forEach(tweet => {
tweetList.push(tweet.text);
})
this.getAllTweetsRecursively(ticker, newNextToken, tweetList);
}
else {
resolve(cleanedTweets)
}
})
}
Alternative implementation – same issue
public async getTweetList(ticker: string): Promise<string[]>{
return new Promise(async (resolve) => {
let tweets: string[] = [];
tweets = await this.getAllTweetsRecursively(ticker, null, tweets);
resolve(tweets);
})
}
public async getAllTweetsRecursively(ticker: string, nextToken: string, tweetList: string[]): Promise<string[]>{
return new Promise(async (resolve, reject) => {
let query = `?query=(${ticker})`
query += this.extraQuery;
if(nextToken){
query += this.nextTokenQuery + nextToken
}
let res = await axios.default.get(this.url + query, {
headers: this.headers
})
let newNextToken = res.data.meta.next_token;
if(res.data.data.length > 0 && newNextToken){
res.data.data.forEach(tweet => {
tweetList.push(tweet.text);
})
await this.getAllTweetsRecursively(ticker, newNextToken, tweetList);
}
else {
let cleanedTweets: string[] = [];
tweetList.forEach(tweet => {
if(tweet.startsWith("RT")){
return;
}
if(!tweet.toLowerCase().includes("$" + ticker)){
return;
}
cleanedTweets.push(tweet);
});
resolve(cleanedTweets)
}
})
}
2
Answers
The issue is that the inner promise was not resolving in the case where it recursively called itself.
Adding
resolve(await this.getAllTweetsRecursively(ticker, newNextToken, tweetList));
fixed the problem.Remember that
async function() {}
already defines a functions that return a Promise, because that’s what theasync
keyword does for you. It makes your function body asynchronous by wrapping it in a Promise without you have to write the promise code yourself. And then for convenience, theawait
keyword is equivalent to Promise.then() without having to write the extraction code yourself.As such, the following two functions are identical:
Both return a Promise, both can be
await
ed inside anotherasync
function, and both can be.then()
chained, because they are literally the same thing. So in the case of your code, if you want to useasync
/await
you should not also build Promises:Your code contains
async getAllTweetsRecursively(...) { return new Promise(async (resolve, reject) => { ... }))
which will now return THREE nested promises, because thoseasync
keywords make this:which because of that
async
further unrolls to:this way lies madness =)