iT邦幫忙

0

請問 jQuery .ajax() 如何設定 success 時回傳的資料格式為 blob,error 時回傳的資料格式為 json?

  • 分享至 

  • xImage

以下是程式碼:

postvalue = {};
request = $.ajax({
  url: "apiUrl",
  type: "post",
  data: JSON.stringify(postvalue),
  contentType: "application/json; charset=utf-8",
  xhrFields: {
    responseType: 'blob'
  },
  success: function (blob, status, xhr) {
    //check for a filename
    var disposition = xhr.getResponseHeader('Content-Disposition');
    if (disposition && disposition.indexOf('attachment') !== -1) {
      disposition = disposition.split("'");
    }
    filename = decodeURIComponent(disposition[2]);
    if (typeof window.navigator.msSaveBlob !== 'undefined') {
      window.navigator.msSaveBlob(blob, filename);
    } else {
      var URL = window.URL || window.webkitURL;
      var downloadUrl = URL.createObjectURL(blob);

      if (filename) {
        // use HTML5 a[download] attribute to specify filename
        var a = document.createElement("a");
        // safari doesn't support this yet
        if (typeof a.download === 'undefined') {
          window.location.href = downloadUrl;
        } else {
          a.href = downloadUrl;
          a.download = filename;
          document.body.appendChild(a);
          a.click();
        }
      } else {
        window.location.href = downloadUrl;
      }
    }
  },
  error: function (result) {
    console.log(result.responseText);
  },
});

在上述程式碼中,設定了
xhrFields: {
responseType: 'blob'
},
成功success(Status Code: 200) API 會回傳 blob 資料,接著進行處理取得檔名和下載的動作,這部分沒有問題。

問題出在如果失敗error(Status Code: 401) API 會回傳 json 資料,透過瀏覽器開發工具 Fetch/XHR 查看該 API 的 preview 和 respones 內容都可以看到正確的 json/字串值,例如: {error:"錯誤"},
但是 console.log(result.responseText); 輸出卻是 "[object Blob]" 這樣的字串值,
(且用 console.log(typeof result.responseText); 檢查,輸出的結果是 string 不是 blob)

如果我把設定:responseType: 'blob' 移除,
error 的 console.log(result.responseText); 輸出就是正確的字串值({error:"錯誤"})
但是 success 裡面這行 var downloadUrl = URL.createObjectURL(blob); 就會出現錯誤:Uncaught TypeError: Failed to execute 'createObjectURL' on 'URL': Overload resolution failed.

請問有沒有辦法可以個別設定 success / error 回傳的資料格式為 blob / json?

圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

1 個回答

6
japhenchen
iT邦超人 1 級 ‧ 2023-03-27 07:40:06
最佳解答

回傳一律用json,succeed回傳

{ result : "ok" , code: 0 , data: blob }

error 則

{ result: "error" , code : 999 , data : null }

收到json回傳,再用javascript 判斷code 多少再看要不要把data讀出來

看更多先前的回應...收起先前的回應...
Wang.W iT邦新手 5 級 ‧ 2023-03-27 12:25:11 檢舉

謝謝,你的方式是可行的,可以把blob資料以base64的方式回傳於json內容中,

以下提供修改方式做參考:

// 原本的程式碼這一段不需要了,filename也可以直接回傳於json:
// check for a filename
var disposition = xhr.getResponseHeader('Content-Disposition');
if (disposition && disposition.indexOf('attachment') !== -1) {
  disposition = disposition.split("'");
}
filename = decodeURIComponent(disposition[2]);

改成

// 將base64轉換成Blob
const byteChars = atob(JsonBase64Contents);
const byteNumbers = new Array(byteChars.length);
for (let i = 0; i < byteChars.length; i++) {
  byteNumbers[i] = byteChars.charCodeAt(i);
}
const byteArr = new Uint8Array(byteNumbers);
const blob = new Blob([byteArr], { type: 'application/vnd.ms-excel' });

filename = JsonFilename;

另一種消極的做法就是,如果error的話
就用 responseType: 'json' 再 post 一次,取得可解析的回傳值

淺水員 iT邦大師 6 級 ‧ 2023-03-27 12:31:15 檢舉

如果因為某些緣故無法改後端的 API
直接用 fetch 也不難

fetch('apiUrl', {
    method: 'post',
    headers: {
        'Content-Type': 'application/json; charset=utf-8'
    },
    body: JSON.stringify(postvalue)
}).then(r => {
    return new Promise((resolve, reject) => {
        if (r.status === 200) {
            r.blob().then(x => resolve(x));
        } else {
            r.json().then(x => reject(x));
        }
    })
}).then(blob => {
    console.log('success', blob);
}, json => {
    console.log('error', json);
});
Wang.W iT邦新手 5 級 ‧ 2023-03-27 20:19:21 檢舉

非常感謝指導!這個做法確實能解決 API 無法修改的困境!

Wang.W iT邦新手 5 級 ‧ 2023-03-27 21:07:54 檢舉

自己幫自己補充 axios 的作法:
要先載入 axios 套件

<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
axios({
  method: 'post',
  url: 'apiurl',
  data: postvalue,
  responseType: 'blob',
  headers: {
    'Content-Type': 'application/json; charset=utf-8'
  }
}).then(response => {
  let disposition = response.headers['content-disposition'];
  let filename = 'unknown';
  if (disposition) {
    //check for a filename
    if (disposition && disposition.indexOf('attachment') !== -1) {
      disposition = disposition.split("'");
    }
    filename = decodeURIComponent(disposition[2]);
  }
  const blob = new Blob([response.data], { type: response.headers['content-type'] });
  console.log('success', filename, blob);
  //處理檔案下載的程式碼片段(省略)
}).catch(error => {
  $("#loading").hide();
  //console.log('error', error);
  let reader = new FileReader();
  reader.addEventListener("loadend", function () {
    let text = reader.result;
    let errorJSON = JSON.parse(text);
    console.log(errorJSON);
  });
  reader.readAsText(error.response.data);
});

我要發表回答

立即登入回答