前端上传数据-按解析顺序

前言

后后需要支持excel上传内容,格式如下:

由于我司多媒体文件在七牛保存,若后端上传数据,则要先保存到后端服务器,存储消耗不容忽视;而且上传数据可能会失败,然后涉及记录失败条目、失败重传、当前进度等
开发周期较长,因此小组讨论后决定采用比较简单的策略:

  1. 按 excel 行顺序上传,提示当前正在上传的行号
  2. 单选上传多媒体资源完成后,将内容保存到数据库
  3. 记录失败的行,上传结束后给出提示,用户自行重传失败的条目

然而理想总是丰满的,现实总是骨感的,实现时才发现不是看上去那么容易。
主要问题:

  1. 我们要等单个 row 里的资源全部上传到七牛后,再将数据 post 到后台,而 js 异步执行,无法保证按excel行顺序上传
  2. 用户选择的资源可能并不是真正的多媒体文件,使用 js 的 file.type 方法获取的文件类型可能不准确,比如将 .xlsx 改为 .jpg, file.type 得到的类型是image/jpeg
  3. 用户在表里填写了文件名,实际上传时可能漏传一些文件,这时候即便成功,也是错误的数据

所谓兵来将挡水来土掩,对以上问题,摸索出解决方案如下:

  1. 使用 Promise 模型,多层嵌套
  2. 根据文件后缀和二进制头,双重判断文件类型
  3. 用户选择文件后即进行校验,缺少文件则无法上传

Promise 模型

js 的Promise支持链式调用,因此单个 row 的资源文件,调用 Promise.all() 全部上传到七牛后,再将该行内容发送给后端,然后进行下一步。

发送数据给后端

例如发送数据给后端可以这样实现

// 声明一个返回 promise 的函数
function sendToDB(data){
  return new Promise((resolve, reject) => {
    createArticleApi(data)
    .then(resp=>{
      resolve()
    })
    .catch(err => {
      reject()
    })
  })
}
// 执行
arr.reduce(
  (promise, data) => {
    return promise.then(() => {
      sendToDB(data)
    })
  }, Promise.resolve()
)
.then(data => {})
.catch(err => {})

然后上传的时候调用链

promise.resolve(row1).then(row2).then(row3).then(row4)...

但实际上传时,由于Promise链上的任一reject会触发catch异常,而发送给后端可能返回失败,导致excel未全部上传完毕就提前退出,因此每个Promise要单独执行,出错后能控制继续执行 or 终止

这里要用到js 语法 (function f(){})() 声明并立即调用函数,函数执行完后返回的promise决定是否继续

let idx = 1
// 直接调用第一个promise, 启动
let p = new Promise(resolve => {
  resolve(sendToDB(arr[0]))
})
// 若写入数据库出错,返回resolve继续执行
while(idx <= arr.length){
  (function(idx){
    p = p.then(() => {
      sendToDb(arr[idx])
      .then(()=>{})
      .catch(()=>{})
      if(idx === arr.length){
        return
      }
      return arr[idx]
    }).catch(() => {
      if(idx === arr.length){
        return
      }
      // 忽略错误,继续执行
      return Promise.resolve()
    })
  })(idx)
  idx += 1
}

上传的时候执行类似

promise.resolve(row1.then(resolve))
.then(row2.catch(resolve))
.then(row3.catch(resolve))
.then(row4.then(resolve))...

上传文件到七牛

而前面封装好的七牛接口,返回的也是promise对象,而我要在调用时才拿返回结果,所以需要一个返回七牛上传结果的函数

js 的闭包可实现这个功能,调用函数后返回一个返回promise的函数

闭包

// 七牛上传接口为异步
// promise 组确保每一行的资源上传完成后,才开始下一步执行
function promiseFunc(line){
  return function(){
    return new Promise((resolve, reject) => {
      // 当前上传的行号
      _this.uploadingLineNo = line.lineno
      let imgsPromises = line.imgs.map(name => {
          return _this.uploadSingleFile(name, 'image')
      })

      let mediasPromises = line.medias.map(name => {
          return _this.uploadSingleFile(name, 'video')
      })

      Promise.all([Promise.all(imgsPromises), Promise.all(mediasPromises)])
      .then(data=>{ 
        line.imgUrls = data[0]
        line.mediaUrls = data[1]
        resolve(line)
      })
      .catch(err => {
        // 行号和对应的上传结果
        _this.resourceFileResult.push({
          lineno: line.lineno,
          error: err || '包含不允许的文件类型'
        })
        line.imgUrls = []
        line.mediaUrls = []
        reject()
      })
    })
  }
} 

然后将excel内容映射成上传函数,推入数组,接下来使用下标调用函数

核心代码

// promise 队列,按 excel 顺序上传内容
let asyncArr = _this.fileContentList.map(promiseFunc)
let idx = 1
// 直接调用第一个promise, 启动
let p = new Promise(resolve => {
  resolve(asyncArr[0]())
})
// 若上传七牛或 写入数据库出错,返回resolve继续执行
while(idx <= asyncArr.length){
  (function(idx){
    p = p.then(data => {
      _this.sendToDB(data)
      .then(()=>{})
      .catch(()=>{})
      if(idx === asyncArr.length){
        return
      }
      return asyncArr[idx]()
    }).catch(() => {
      if(idx === asyncArr.length){
        return
      }
      // 忽略上传七牛错误,继续执行
      return Promise.resolve()
    })
  })(idx)
  idx += 1
}
posted @ 2019-01-31 14:56  葡萄不吐皮  阅读(1914)  评论(0编辑  收藏  举报