vscode中使用node服务调试,会在promise的reject出现断点报错

本文探讨了在VSCode中调试Promise时遇到的错误,并详细分析了错误原因。该问题出现在使用koa框架并大量依赖Promise、async和await时。文章提供了代码示例,并给出了解决方案:调整VSCode的调试设置。

vscode

版本 1.19.2

提交 490ef761b76b3f3b3832eff7a588aac891e5fe80
日期 2018-01-10T16:16:25.767Z
Shell 1.7.9
渲染器 58.0.3029.110
Node 7.9.0
架构 ia32

 

昨天在使用vscode写node后台服务的时候出现了一个莫名其妙的bug:代码执行到promise内部,然后执行reject的时候vscode产生断点报错。通过命令行启动程序不会报错,但是通过vscode F5启动调试就会出现这个错误。

因为后台框架使用的是koa,所以大量的使用了Promise、async和await,然后就出现了这个问题,代码如下

  checkLogin: async (ctx, next) => {
    ctx.status = 200
    try {
      let decoded = await checkToken(ctx.request.body.token)
      ctx.body = JSON.stringify({state: 'OK'})
    }
    catch(err) {
      console.log(err)
      ctx.body = JSON.stringify({error: err})
    }
  }

  /**
   * 检查token是否可用
   */
  checkToken: (token) => {
    return new Promise((resolve, reject) => {
      if (token) {
        jwt.verify(token, 'zyx', (err, decoded) => { //解码token
          if (err) {
            return reject(err)
          }
          resolve(decoded)
        })
      } else {
        reject('NO_TOKEN')
      }
    })
  }

然后代码运行时报错如下(jsonwebtokenerror是err的类型,可以无视,因为不管reject传入的是什么,都会报相应数据类型的异常错误)

而且经过测试,不管在promise的何处调用reject都是会报相同的错误

最后在google上找到了具体的答案,连接在此

原文的解释是:
If a promise is rejected before an error handler is attached, the debugger will break, even if only "uncaught exceptions" is checked. If it's rejected after the error handler is attached, it works as expected. And really the problem is the way the promise doesn't know whether its rejection will be handled or not.

大致的意思就是说:
如果在promise处理异常之前就执行了jeject,vscode就会认为这个promise没有处理异常的过程,然后就报错了。

换句话说只要没有在promise的后面紧跟.catch或.then处理异常就会触发这个错误机制。而且不能在同步过程中触发reject,因为这个时候异常处理还没有挂载到promise上

function f1() {
  new Promise((resolve, reject) => {
    reject() //报错
  }).catch(e=>{console.log(e)})
}
 
function f1() {
  new Promise((resolve, reject) => {
    setTimeout(()=>{reject()},0) //异步执行后不报错
  }).catch(e=>{console.log(e)})
}
 
f1()


最后关于这个问题的解决方法:

这个问题应该算是vscode的一个关于js promise 的 bug,而我的代码是没有问题的,没有必要为了避免这个异常断点而hack来写代码。所以直接修改vscode的断点机制就可以了。

 

打开vscode的调试工具栏

然后在左下角取消选中 ‘未捕获的异常’ 选项

这样就解决了问题。

转载于:https://www.cnblogs.com/zhaozhipeng/p/8350197.html

<think>嗯,我现在需要在Vue2和Node.js项目中实现视频的分片传输和断点续传。首先,我得理解这两个概念。分片传输应该是指将大文件分成小块逐个上传,而断点续传就是上传中断后可以从中断的地方继续,对吧? 那分片传输具体怎么操作呢?前端应该用JavaScript把文件切片。可能需要用到File API,比如File.slice方法。比如,把视频文件切成每块5MB的大小。然后每个分片上传的时候要带上一些信息,比如当前是第几个分片,总共有多少分片,还有文件的唯一标识符,这样后端才能知道这些分片属于哪个文件。 然后,前端用Vue2的话,应该用axios发送每个分片。可能需要设置并发控制,同时上传几个分片,避免同时太多请求。不过并发控制可能需要前端自己管理,比如用Promise.all或者限制同时上传的数量。 后端的话,Node.js处理这些分片。每个分片上传过来,需要根据唯一标识符存到一个临时文件夹里。同时要记录已经上传的分片,这样前端可以查询还有哪些分片没传,实现断点续传。比如,用Redis或者数据库来记录分片的上传情况,或者直接在文件系统里记录。 合并分片的话,当所有分片都上传完成后,后端需要按顺序把这些分片合并成一个完整的文件。Node.js里面可以用fs模块,比如用appendFileSync逐个追加分片内容。这时候要注意文件顺序,可能需要根据分片的索引来排序。 断点续传的关键在于,前端在上传前先询问后端已经上传了哪些分片,然后只上传剩下的。这需要在后端提供一个接口,比如GET /upload/status,参数是文件hash,返回已上传的分片列表。前端根据这个列表过滤掉已经上传的部分,继续传剩下的。 另外,文件的唯一标识符怎么生成呢?可能需要用文件的hash,比如用SparkMD5计算文件的MD5,这样即使文件名不同,只要内容相同就可以复用。不过计算整个文件的hash可能会比较耗时,特别是大文件。可能需要用Web Worker在后台计算,避免阻塞主线程。 还有分片的大小设置,太大的话分片数量少,但每次上传失败重试的成本高;太小的话分片太多,管理起来麻烦。通常可能选1MB到5MB之间,根据实际情况调整。 前端上传的时候,每个分片作为一个独立的请求,可能需要用FormData来封装分片数据、文件名、分片索引等信息。比如: let formData = new FormData(); formData.append('file', chunk); formData.append('hash', fileHash); formData.append('index', chunkIndex); 然后后端接收到这些数据后,保存分片到以hash命名的临时目录,并记录分片索引。合并的时候按顺序读取所有分片,合并成最终文件。 可能遇到的问题包括分片顺序错乱,导致合并后的文件损坏。所以后端保存分片的时候,需要确保按索引顺序合并。或者每个分片的文件名包含索引,这样合并时可以按顺序读取。 还有并发上传的问题,如果同时上传多个分片,服务器处理是否会有问题?Node.js是单线程,但IO操作是异步的,可能需要控制并发量,或者使用队列来处理。 另外,前端如何检测到上传中断?可能需要在上传失败时捕获错误,然后保存已上传的分片信息,比如存在localStorage或者Vuex里,下次继续上传。 测试的时候,可能需要模拟上传中断的情况,比如关闭浏览器或网络,然后重新上传,看是否能继续。 总结一下,步骤大概是: 前端: 1. 用户选择文件,计算文件hash作为唯一标识。 2. 将文件切片,每片一个Blob对象。 3. 询问后端该文件已上传的分片列表。 4. 上传未上传的分片,每个分片携带hash、索引等信息。 5. 全部分片上传完成后,通知后端合并。 后端: 1. 提供接口检查分片上传状态。 2. 接收分片并保存到临时目录。 3. 合并分片接口,将所有分片合并为完整文件。 4. 清理临时文件。 可能还需要考虑文件上传的进度显示,前端可以通过监听每个分片的上传进度,计算总进度。 那具体的代码实现呢?比如前端用Vue2,如何选择文件并切片?可能需要用input type="file",然后监听change事件,获取文件对象。然后用FileReader或者直接使用Blob.slice方法切片。 比如: // 选择文件 <input type="file" @change="handleFileSelect"> methods: { handleFileSelect(event) { const file = event.target.files[0]; if (!file) return; // 计算文件hash this.calculateFileHash(file).then(hash => { this.fileHash = hash; // 切片 const chunkSize = 5 * 1024 * 1024; // 5MB const chunks = []; let start = 0; while (start < file.size) { const chunk = file.slice(start, start + chunkSize); chunks.push(chunk); start += chunkSize; } this.chunks = chunks; // 检查上传状态 this.checkUploadStatus(); }); } } 然后checkUploadStatus方法调用后端的接口,获取已上传的分片索引,过滤出需要上传的分片,开始上传。 上传分片的时候,可以用axios.post,每个分片一个请求,可能需要限制并发数,比如用p-limit库控制同时最多3个请求。 合并分片的话,后端收到请求后,读取临时目录中的所有分片,按索引排序,然后逐个追加到目标文件中。 Node.js部分,比如用Express: app.post('/upload', async (req, res) => { const { hash, index } = req.body; const chunk = req.files.file.data; const chunkDir = path.join(__dirname, 'temp', hash); if (!fs.existsSync(chunkDir)) { fs.mkdirSync(chunkDir, { recursive: true }); } // 保存分片 fs.writeFileSync(path.join(chunkDir, index), chunk); // 记录已上传的索引 // 可以用数据库或者文件记录 res.send({ code: 0, message: '上传成功' }); }); app.get('/upload/status', (req, res) => { const { hash } = req.query; const chunkDir = path.join(__dirname, 'temp', hash); if (!fs.existsSync(chunkDir)) { return res.send({ uploaded: [] }); } const uploaded = fs.readdirSync(chunkDir).map(Number); res.send({ uploaded }); }); app.post('/merge', (req, res) => { const { hash, filename } = req.body; const chunkDir = path.join(__dirname, 'temp', hash); const chunks = fs.readdirSync(chunkDir); // 按索引排序 chunks.sort((a, b) => a - b); const filePath = path.join(__dirname, 'uploads', filename); chunks.forEach(chunkIndex => { const chunkPath = path.join(chunkDir, chunkIndex); const data = fs.readFileSync(chunkPath); fs.appendFileSync(filePath, data); // 删除分片 fs.unlinkSync(chunkPath); }); // 删除临时目录 fs.rmdirSync(chunkDir); res.send({ code: 0, message: '合并成功' }); }); 不过这里可能有些问题,比如分片保存的文件名是索引,应该用数字排序。合并时可能需要确保顺序正确。另外,处理大文件时同步读写可能导致性能问题,可能需要用流(stream)来读写,避免内存溢出。 比如用fs.createReadStream和fs.createWriteStream来合并分片: const writeStream = fs.createWriteStream(filePath); for (const chunkIndex of chunks) { const chunkPath = path.join(chunkDir, chunkIndex); const readStream = fs.createReadStream(chunkPath); readStream.pipe(writeStream, { end: false }); await new Promise(resolve => readStream.on('end', resolve)); } writeStream.end(); 这样可能更高效。 另外,计算文件hash的时候,如果文件很大,可能需要分片读取计算,比如用FileReader逐片读取,更新hash。比如用SparkMD5: calculateFileHash(file) { return new Promise((resolve, reject) => { const chunkSize = 2 * 1024 * 1024; // 2MB chunks const chunks = Math.ceil(file.size / chunkSize); const spark = new SparkMD5.ArrayBuffer(); let currentChunk = 0; const reader = new FileReader(); reader.onload = function(e) { spark.append(e.target.result); currentChunk++; if (currentChunk < chunks) { loadNext(); } else { resolve(spark.end()); } }; reader.onerror = reject; function loadNext() { const start = currentChunk * chunkSize; const end = start + chunkSize >= file.size ? file.size : start + chunkSize; reader.readAsArrayBuffer(file.slice(start, end)); } loadNext(); }); } 这样就能计算出整个文件的hash值。 另外,需要考虑文件上传的暂停和恢复,前端可能需要维护一个上传队列,并且允许用户暂停,此时中止正在上传的分片请求,保存已上传的信息,下次继续。 总之,整个过程需要前后端配合,合理设计接口和数据格式,处理好分片的上传、记录、合并,以及断点续传的状态查询。</think>在Vue2和Node.js中实现视频分片传输与断点续传,需分为前端切片上传和后端合并管理两部分。以下是分步实现方案: --- ### 一、前端实现(Vue2) #### 1. 文件切片与哈希计算 ```javascript // 计算文件MD5(使用SparkMD5库) calculateHash(file) { return new Promise((resolve) => { const chunks = Math.ceil(file.size / 2 * 1024 * 1024); const spark = new SparkMD5.ArrayBuffer(); let currentChunk = 0; const reader = new FileReader(); reader.onload = (e) => { spark.append(e.target.result); currentChunk++; if (currentChunk < chunks) this.loadNextChunk(file, reader, currentChunk); else resolve(spark.end()); }; this.loadNextChunk(file, reader, 0); }); } // 文件切片(示例:5MB/片) sliceFile(file, chunkSize = 5 * 1024 * 1024) { const chunks = []; let start = 0; while (start < file.size) { chunks.push({ chunk: file.slice(start, start + chunkSize), index: start / chunkSize // 分片序号 }); start += chunkSize; } return chunks; } ``` #### 2. 查询上传状态 ```javascript // 向后端查询已上传分片 async checkUploadStatus(fileHash) { const { data } = await axios.get('/api/upload/status', { params: { hash: fileHash } }); return data.uploadedIndexes; // 例如 [0,1,2] } ``` #### 3. 分片上传(含并发控制) ```javascript async uploadChunks(chunks, fileHash, uploadedIndexes) { const requests = chunks .filter(chunk => !uploadedIndexes.includes(chunk.index)) .map(chunk => { const formData = new FormData(); formData.append('chunk', chunk.chunk); formData.append('hash', fileHash); formData.append('index', chunk.index); return axios.post('/api/upload', formData); }); // 控制并发数为3 const pool = new ConcurrentPool(3); await pool.run(requests); // 所有分片上传完成后触发合并 await axios.post('/api/merge', { hash: fileHash, fileName: file.name }); } ``` --- ### 二、后端实现(Node.js) #### 1. 保存分片接口 ```javascript const express = require('express'); const fs = require('fs'); const path = require('path'); app.post('/api/upload', (req, res) => { const { hash, index } = req.body; const chunk = req.files.chunk; const tempDir = path.join(__dirname, 'temp', hash); // 创建临时目录 if (!fs.existsSync(tempDir)) fs.mkdirSync(tempDir, { recursive: true }); // 保存分片(文件名用索引标识) fs.writeFileSync(path.join(tempDir, index), chunk.data); res.send({ code: 0 }); }); ``` #### 2. 分片状态查询接口 ```javascript app.get('/api/upload/status', (req, res) => { const { hash } = req.query; const tempDir = path.join(__dirname, 'temp', hash); if (!fs.existsSync(tempDir)) { return res.send({ uploadedIndexes: [] }); } const uploadedIndexes = fs.readdirSync(tempDir).map(Number); res.send({ uploadedIndexes }); }); ``` #### 3. 合并分片接口 ```javascript app.post('/api/merge', async (req, res) => { const { hash, fileName } = req.body; const tempDir = path.join(__dirname, 'temp', hash); const targetPath = path.join(__dirname, 'uploads', fileName); // 获取所有分片并按序号排序 const chunks = fs.readdirSync(tempDir) .sort((a, b) => a - b); // 流式合并(避免内存溢出) const writeStream = fs.createWriteStream(targetPath); for (const chunk of chunks) { const chunkPath = path.join(tempDir, chunk); await new Promise(resolve => { const readStream = fs.createReadStream(chunkPath); readStream.pipe(writeStream, { end: false }); readStream.on('end', () => { fs.unlinkSync(chunkPath); // 删除分片 resolve(); }); }); } fs.rmdirSync(tempDir); // 删除临时目录 res.send({ code: 0 }); }); ``` --- ### 三、关键优化点 1. **断点续传**:通过哈希值唯一标识文件,查询已上传分片列表。 2. **分片顺序控制**:后端按索引顺序合并,避免视频损坏。 3. **流式合并**:使用Node.js流(Stream)处理大文件,降低内存占用。 4. **并发控制**:前端限制并发请求数,减轻服务器压力。 --- ### 四、可能遇到的问题 1. **哈希计算卡顿**:大文件计算MD5时,使用Web Worker避免阻塞主线程。 2. **分片丢失**:后端需做超时清理,定期删除未合并的临时分片。 3. **网络重试**:前端需实现分片上传失败后的自动重试机制。 通过上述方案,可有效实现大视频文件的稳定上传与断点恢复。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值