前端上傳數據-按解析順序


前言

后后需要支持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
}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM