如何正确通过fetch请求构建完整的歌曲元数据数组?
问题:音乐播放器元数据获取不完整、异步访问异常
我正在开发一款音乐播放器,歌曲存放在music文件夹中。后端已实现获取歌曲元数据的接口,我拥有一个歌曲路径数组,希望通过该数组调用后端接口获取每首歌的元数据。目前使用forEach循环处理每个路径对应的fetch请求,但构建出的元数据数组始终不完整,每次运行得到的元素数量都不同;直接打印数组首个元素会返回undefined,仅在稍作等待后才能正常获取。
歌曲总数为22首,测试时第一次获取到10条元数据,第二次仅获取到6条。
后端代码
const express = require('express'); var fs = require('fs'); const { readdir } = fs.promises; const path = require('path'); const cors = require('cors'); const jsmediatags = require('jsmediatags'); const app = express(); const port = 5783; app.use(cors()) app.use(express.json()); app.get('/getsongs', async(req, res) => { try { let files = []; const getFileList = async(dirName) => { const items = await readdir(dirName, { withFileTypes: true }); for (const item of items) { if (item.isDirectory()) { files = [...files, ...(await getFileList(`${dirName}/${item.name}`)), ]; } else { files.push(`${dirName}/${item.name}`); } } return files; }; getFileList(path.join(__dirname, '../music/')).then((files) => { let songsPathsForMD = []; let songsPaths = [] const getrelPath = async(files) => { await files.forEach(file => { const relativePath = '../music/' + file.slice(82); const newPath = relativePath.replace(/ +/g, '%20'); songsPathsForMD.push(relativePath) songsPaths.push(newPath) }) return { songsPathsForMD, songsPaths }; } getrelPath(files).then((hello) => { return res.status(200).send(hello) }) }); } catch (error) { return res.status(400).json({ error: error }) } }) app.post('/getmdata', (req, res) => { const songPath = req.body.songpath; try { const addMdata = async(tag, songpath) => { const data = tag.tags.picture.data; let base64String = ""; for (let index = 0; index < data.length; index++) { base64String += String.fromCharCode(data[index]) } let md = await { songPath: songpath.replace(/ +/g, '%20'), songTitle: tag.tags.title, songArtist: tag.tags.artist, songAlbum: tag.tags.album, coverFormat: tag.tags.picture.format, coverData: base64String, songDuration: 0 } return md } jsmediatags.read(songPath, { onSuccess: async(tag) => { await addMdata(tag, songPath).then((mdata) => { return res.status(200).send(mdata) }) }, onError: function(error) { console.log(':(', error.type, error.info); } }); } catch (error) { return res.status(400).json({ error: error }) } }) app.listen(port, () => { console.log(`The backend started at http://localhost:${port}`); })
前端请求代码
const getSongsPathList = async() => { const response = await fetch('http://localhost:5783/getsongs') const jsonData = await response.json(); console.log(jsonData); return jsonData; } getSongsPathList().then(async(pathArray) => { const { songsPaths, songsPathsForMD } = pathArray; console.log(songsPathsForMD); let songsData = []; await songsPathsForMD.forEach(async path => { const response = await fetch('http://localhost:5783/getmdata', { method: 'POST', headers: { "Content-Type": "application/json", }, body: JSON.stringify({ songpath: path }) }) const jsonResponse = await response.json(); await songsData.push(jsonResponse); }); console.log('hello'); console.log(songsData); console.log(songsData[0]); // showing undefined setTimeout(() => { console.log(songsData[0]); //it is logging the first element }, 10); }).catch(err => { console.log(err); });
解决方案
核心问题
forEach循环不支持异步等待,它会直接遍历数组触发所有异步请求,但不会等待这些请求完成,await songsPathsForMD.forEach(...)代码不会起到等待作用,导致后续打印songsData时,大部分请求还未返回结果,数组自然不完整。
修改后的前端代码
const getSongsPathList = async() => { const response = await fetch('http://localhost:5783/getsongs') const jsonData = await response.json(); console.log(jsonData); return jsonData; } getSongsPathList().then(async(pathArray) => { const { songsPaths, songsPathsForMD } = pathArray; console.log(songsPathsForMD); // 用map生成所有请求的Promise数组,再通过Promise.all等待全部完成 const songsData = await Promise.all( songsPathsForMD.map(async path => { const response = await fetch('http://localhost:5783/getmdata', { method: 'POST', headers: { "Content-Type": "application/json", }, body: JSON.stringify({ songpath: path }) }) return response.json(); }) ); console.log('hello'); console.log(songsData); console.log(songsData[0]); // 现在可正常获取首个元素 }).catch(err => { console.log(err); });
后端代码优化(可选)
/getsongs接口中的getrelPath函数无需异步标记,因为内部没有异步操作,可简化为:
const getrelPath = (files) => { let songsPathsForMD = []; let songsPaths = [] files.forEach(file => { const relativePath = '../music/' + file.slice(82); const newPath = relativePath.replace(/ +/g, '%20'); songsPathsForMD.push(relativePath) songsPaths.push(newPath) }) return { songsPathsForMD, songsPaths }; } // 调用时直接返回结果,无需then res.status(200).send(getrelPath(files));
效果说明
修改后,Promise.all会等待所有元数据请求完成后,返回完整的结果数组,此时可以直接访问数组内的所有元素,无需额外等待。
内容的提问来源于stack exchange,提问作者Arvind
相关产品推荐
相关产品推荐

