You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何正确通过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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.28 09:44:59