web

Callback or Promise?

异步处理该如何选择?

Posted by Youga on May 12, 2018

前端开发中有一些 Api 需要通过回调处理一些异步操作,但是我们更愿意使用 Promise的调用方式。那么什么时候我们使用回调函数什么时候使用Promise呢?

callback函数转换为Promise

假设我需要计算文件sha256值,由于读取文件是异步操作,我需要在onloadend的回调结果中处理reader.result

uploadFile(file){
  let reader = new FileReader()
  reader.readAsBinaryString(file)
  reader.onloadend = () = >{
    let hashCode = this.SHA256.convertToSHA256(reader.result)
    // todo
  }
}

使用async函数,await转换

async uploadFile(file) {
  let fileBlob = await new Promise((resolve,reject)=>{
    let reader = new FileReader()
    reader.readAsBinaryString(file)
    reader.onloadend = () = >{
      resolve(reader.result)
    }
  })
  this.SHA256.convertToSHA256(fileBlob)
  // todo
}

自定义Sleep函数

function Sleep(micsec) {
  return new Promise((resolve) => {
    setTimeout(resolve, micsec);
  });
}

使用Promise避免多层嵌套的回调

// 处理前
() => {
  A.callback = (B) => {
    B.callback = (C) => {
      C.callback = (D) => {
        // .....
      };
    };
  };
};
// 处理后
async () => {
  let B = await new Promise((resolve, reject) => {
    A.callback = resolve;
  });
  let C = await new Promise((resolve, reject) => {
    B.callback = resolve;
  });
  // ...
};

callback一定可以转化成Promise写法?

答案是 no!回调函数和 Promise 本质上不是一回事,能够相互转化的场景是因为两者都能处理异步场景。

回调函数和 Promise 的区别至少在于:

  • callback可以多次被回调并传入不同值

  • promise拥有不可逆状态

从 pending->fulfilled, pending->rejected 的状态是不可逆的。譬如:

new Promise((res) => {
  console.log("only printed once.");
  res("finish.");
})
  .then((res) => {
    console.log(1, res);
  })
  .then((res) => {
    console.log(2, res);
  });
// 依次打印的值是:
// only printed once.
// 1 finish.
// finish.

可知,单个Promise状态是不能改变的(链式调用过程中可以改变 promise 状态),并且 value 或者 error 会被缓存下来。这个特性在需要设计中可以被利用。

callback是可以被多次执行的,其参数相当于传递给下一层的 value 或 error 却是可以被多次改变的。例如:

function loadData(callback) {
  let finishLoad = false;
  // 从缓存加载数据,一般比网络加载要快
  loadAtStorage().then((data) => {
    if (finishLoad || !data) {
      return;
    }
    callback(data);
  });
  // 从网络加载
  loadAtNetwork().then((data) => {
    finishLoad = true;
    callback(data);
  });
}

按正常情况看,加载数据函数中callback函数会先后被回调两次。第一次先使用本地数据同时等待网络数据加载完毕,更新网络数据。