docs: function (task, chunk, callback) {
          var that = this;
          var formData = new FormData();
          var opt = task.opt;
          var file = opt.file;
          var slice_size = opt.slice_size;
          var session = opt.session;
          var totalSize = file.size;
          var offsetStart = chunk.start;
          var offsetEnd = Math.min(offsetStart + slice_size, totalSize);
          var blob = that.slice.call(file, offsetStart, offsetEnd);
          var chunkSize = blob.size;
  
          var removeXhr = function (xhr) {
              for (var i = task.uploadingAjax.length - 1; i >= 0; i--) {
                  if (xhr === task.uploadingAjax[i]) {
                      task.uploadingAjax.splice(i, 1);
                  }
              }
          };
          var preLoaded = 0;
          var updateProgress = function (loaded, immediately) {
              task.loadedSize += loaded - preLoaded;
              preLoaded = loaded;
              task.onTaskProgress && task.onTaskProgress(immediately);
          };
          var uploadChunk = function (cb) {
              formData.append('sliceSize', slice_size);
              formData.append('op', 'upload_slice_data');
              formData.append('session', session);
              formData.append('offset', offsetStart);
              opt.sha && formData.append('sha', opt.sha);
              formData.append('fileContent', blob);
  
              that.getAppSign(function (sign) {
                  opt.sign = sign;
                  var url = that.getCgiUrl(opt.path, opt.sign);
  
                  var ajax = $.ajax({
                      type: 'POST',
                      dataType: "JSON",
                      url: url,
                      data: formData,
                      xhr: function () {
                          var xhr = $.ajaxSettings.xhr();
                          xhr.upload.onprogress = function (evt) {
                              updateProgress(evt.loaded);
                              task.onTaskProgress && task.onTaskProgress();
                          };
                          return xhr;
                      },
                      success: function (res) {
                          updateProgress(chunkSize, true);
                          res = res || {};
                          if (res.code === 0) {
                              cb(null, res);
                          } else {
                              cb('error', res);
                          }
                      },
                      error: function () {
                          updateProgress(0, true);
                          cb('error');
                      },
                      complete: function () {
                          removeXhr(ajax);
                      },
                      processData: false,
                      contentType: false
                  });
                  task.uploadingAjax.push(ajax);
              });
          };
  
          // 失败重试 3 次
          var tryUpload = function (times) {
              if (opt.globalTask.state === 'cancel') return;
              uploadChunk(function (err, data) {
                  if (err) { // fail, retry
                      if (times >= that.uploadMaxRetryTimes || task.uploadError || opt.globalTask.state === 'cancel') {
                          callback(err, data);
                      } else {
                          tryUpload(times + 1);
                      }
                  } else { // success
                      callback(err, data);
                  }
              });
          };
          fileSliceCopy(file, offsetStart, offsetEnd, function (newBlob) {
              blob = newBlob;
              chunkSize = blob.size;
              tryUpload(1);
          });
  
      }