如何将 callback 接口 转化成 Async/Await

背景

今天在使用七牛 nodeJS SDK 做服务器端文件上传时,发现其封装的API,使用起来非常不方便。于是,想将callback API封装成自己习惯的 Async/Await的语法。

问题

我们先看一下七牛代码

var formUploader = new qiniu.form_up.FormUploader(config);
var putExtra = new qiniu.form_up.PutExtra();
var key='test.txt';
formUploader.put(uploadToken, key, "hello world", putExtra, function(respErr,
  respBody, respInfo) {
  if (respErr) {
    throw respErr;
  }
  if (respInfo.statusCode == 200) {
    console.log(respBody);
  } else {
    console.log(respInfo.statusCode);
    console.log(respBody);
  }
});

formUploader.put方法中,最后一个参数是一个回调函数,回调函数的第一个参数是一个 error 参数。这种风格的函数称为 Error-First Callback。它有两个特征:

  • 回调的第一个参数为一个错误对象
  • 第二个参数保留给任何成功的数据

在NodeJS的世界中,我们经常会看到这种写法。但是,这种写法不是很好维护。我们需要在回调中写很多判断。除此之外,我们将异步的业务逻辑,放入了回调中。这样将导致主逻辑不是很清晰。

Node style callbacks (nodebacks) have a particular format where the callbacks is always the last argument and its first parameter is an error.

那么,如何改造这样的写法呢?我们想到了 Async/Await,我们都知道异步操作,在编程中是很难处理的。人们为了解决这个问题,尝试过callback、promise对象、Generator函数,但都不够彻底,使用起来仍不够简洁方便。

异步编程的最高境界,就是根本不用关心它是不是异步。

解决方法

我们将 formUploader.put方法包一层promise,然后在主逻辑中,调用它。


const stream_uploader = (token, key, readableStream) => {
  return new Promise((resolve, reject) => {
    formUploader.putStream(token, key, readableStream, putExtra, (res_error, res_body, res_info) => {
      if (res_error) {
        reject(res_error);
      }
    
      if (res_info.statusCode == 200) {
        reslove(res_body);
      } else {
        reject({
          code: res_info.statusCode,
          body: res_body,
        });
      }
    });
  });
}

const main = async () => {
  // ... 获取token、key业务逻辑

  // 将obj,转化为 readableStream 流
  const readableStream = await intoStream(JSON.stringify(obj));

  try {
    const result = await stream_uploader(token, key, readableStream, putExtra);  
  } catch (error) {
    // 处理异常
  }
};

main();

使用 util.promisify Promise化 nodebacks 函数

const { promisify } = require('util');
const fs = require('fs');

const statAsync = promisify(fs.stat);

statAsync('.').then(stats => {
  console.log(stats);
}).catch((err) => {
  console.log(err);
});

总结

实际上,在工作中不用大量手动将 nodeback(Error-First Callback) Promise化,可以借助工具实现这种目的。例如,BluebirdQ、原生的 util.promisify等。

相关资料

  • Converting geolocation from callbacks into async / await (Javascript)
  • 如何把 Callback 接口包装成 Promise 接口
  • How do I convert an existing callback API to promises?
  • JavaScript — from callbacks to async/await
  • async 函数的含义和用法

你可能感兴趣的:(如何将 callback 接口 转化成 Async/Await)