Node.js中for-of循环仅执行一次问题排查(含API延迟请求)
Fixing Your Node.js Lyric Scraper: For-Of Loop Stops After First Iteration
Let's break down exactly why your loop is exiting early and fix the code to handle bulk requests properly, while respecting rate limits.
Key Issues in Your Code
- Unresolved Promise in
writeSongs: YourwriteSongsfunction returns a Promise but never callsresolve, soawait writeSongs(art)hangs forever. This blocks the for-of loop from moving to the next artist. - Broken Delay Logic: Using
forEachwith fixed delays creates all your timeout Promises at once (they just fire at different times), which doesn't guarantee sequential execution and leaves the upper layers unaware when all requests finish. - Scope Error:
writeFiletries to accessgenswhich is defined only inmain—this would throw a reference error once you fix the loop issue. - Callback Hell: Using callback-based
fsmethods means you can't track when file writes complete, leading to unhandled async behavior.
Fixed Code
const fs = require('fs').promises; // Use promise-based fs to avoid callbacks const { JSDOM } = require("jsdom"); const { window } = new JSDOM(""); const $ = require("jquery")(window); const path = require('path'); const fetchArt = require('./utils/fetchArt.js'); const replacer = RegExp(' ', 'g'); const slash = RegExp('/', 'g'); const requestArtSongs = (artist) => { return $.getJSON(`https://www.vagalume.com.br/${artist}/index.js`).then( (data) => data.artist.lyrics.item ); }; const mapLang = (lang) => { switch (lang) { case 1: return 'pt'; case 2: return 'en'; default: return lang; } }; const requestMusic = (artist, song) => { // Replace {key} with your actual Vagalume API key! return $.getJSON( `https://api.vagalume.com.br/search.php?art=${artist}&mus=${song}&apikey={key}` ).then((data) => [data.mus[0].text, data.mus[0].lang]); }; // Sequential request handling with proper delays const makeRequest = async (art, songs, genre) => { for (const song of songs) { await makeSongRequest(art, song.desc, genre); await delay(3000); // Wait 3s AFTER each request completes } }; const writeFile = async (art, songName, lyricsData, genre) => { const music = { artist: art, song: { name: songName, text: lyricsData[0], lang: mapLang(lyricsData[1]) } }; const artistDir = path.join(__dirname, 'Genders', genre, art); // Create directory if it doesn't exist try { await fs.access(artistDir); } catch { await fs.mkdir(artistDir, { recursive: true }); } const filePath = path.join(artistDir, `${songName.replace(slash, '')}.json`); await fs.writeFile(filePath, JSON.stringify(music, null, 4)); console.log(`Song ${songName} written successfully!`); }; const makeSongRequest = async (art, songName, genre) => { const lyricsData = await requestMusic(art, songName); await writeFile(art, songName, lyricsData, genre); }; const delay = (t) => new Promise(resolve => setTimeout(resolve, t)); const writeSongs = async (artist, genre) => { const formattedArtist = artist.replace(replacer, '-').toLowerCase(); const songs = await requestArtSongs(formattedArtist); await makeRequest(artist, songs, genre); }; const main = async () => { try { const genres = ['Funk']; for (const genre of genres) { const artists = await fetchArt(genre); for (const artist of artists) { await writeSongs(artist, genre); // Wait for all songs of this artist to finish } } console.log('All lyric scraping completed!'); } catch (err) { console.error('Error during scraping:', err); } }; main();
What Changed & Why
- Fixed Promise Resolution:
writeSongsnow usesasync/awaitand returns a proper Promise, soawait writeSongs(art)will only resolve when all requests for that artist are done. - Sequential Requests:
makeRequestusesfor...ofinstead offorEach, ensuring each request finishes (and the 3s delay passes) before starting the next one. This respects rate limits and avoids overwhelming the API. - Fixed Scope Issues: The genre is passed as a parameter through all functions instead of relying on global/outer scope variables.
- Promise-Based File Operations: We use
fs.promisesto eliminate callbacks, making it easy to track when file writes complete and handle errors properly. - Global Error Handling: Added a try/catch in
mainto catch and log any unexpected errors during scraping.
Don't forget to replace {key} in requestMusic with your actual Vagalume API key—without this, your requests will fail.
内容的提问来源于stack exchange,提问作者user14191645
相关产品推荐
相关产品推荐

