Promise 与 Async/await 来控制异步和超时问题 - 图1

读懂文章需要掌握异步控制的两个技术点:

  • Promise
  • async/await

下边单独分开两个问题代码实践

一、超时问题需求背景

某接口请求响应超过指定时间(比如3秒)后,提示超时或者重新请求。或者走其他方式获取数据。

代码实现

提示:利用了Promise状态一旦发生变化,就无法再改变的特点

  1. const delay = ms => new Promise(resolve => setTimeout(resolve, ms));
  2. const timeout = (promise, ms) => new Promise((resolve, reject) => {
  3. promise.then(resolve, reject);
  4. (async () => {
  5. await delay(ms);
  6. reject(new Error('delay error'));
  7. })();
  8. })

测试一下

我们来测试一下

  1. /*
  2. * 规定某时间超时后的行为控制
  3. */
  4. const delay = ms =>
  5. new Promise(resolve => {
  6. console.log("delay function run setTimeout function");
  7. setTimeout(() => {
  8. console.log("delay function setTimeout callback inner");
  9. resolve();
  10. }, ms);
  11. });
  12. const timeout = (promise, ms) =>
  13. new Promise((resolve, reject) => {
  14. console.log(1);
  15. promise.then(resolve, reject);
  16. console.log(2);
  17. (async () => {
  18. console.log("delay before");
  19. await delay(ms);
  20. console.log("delay after");
  21. reject("delay error");
  22. })();
  23. });
  24. // 模拟异步接口请求
  25. const getInfo = () =>
  26. new Promise((resolve, reject) => {
  27. setTimeout(() => {
  28. console.log("getinfo resolve");
  29. resolve("result data");
  30. }, 4000);
  31. });
  32. async function testIIF(ms) {
  33. let result = await timeout(getInfo(), ms);
  34. return result;
  35. }
  36. // 可以修改超时时间为3000,参考不同运行结果
  37. testIIF(4000)
  38. .then(res => {
  39. console.log("接口请求成功=", res);
  40. })
  41. .catch(e => {
  42. console.log("接口超时:", JSON.stringify(e));
  43. });

在线运行:codepen.io在线代码

  • 当delay 时间 ms >= 接口响应时间时,结果正常
0
"delay before"
"delay function"
"getinfo resolve"
"接口请求成功=" "result data"
"delay function setTimeout"
"delay after"
  • 当delay 时间 ms < 接口响应时间时,结果超时
0
"delay before"
"delay function"
"delay function setTimeout"
"delay after"
"接口超时:" "'delay error'"
"getinfo resolve"

二、异步控制问题背景

某接口,假设被限制每秒请求次数(qps等),QPS不了解推荐阅读:吞吐量(TPS)、QPS、并发数、响应时间(RT)概念。有个业务场景,是需要多次请求该接口(循环),但是要控制不能超过QPS,避免接口报错。代码如何控制好,如何更好的写出可维护的高效代码。

业务场景再举例:比如有100张图片,你需要调用百度的OCR接口去识别图片上的文字,但是百度的免费版接口指支持 2 QBS,你循环请求接口的时候就要控制每秒不能超过2次请求,不然会报错。

代码实现

全部代码见:client/ocr.js#L31

该业务控制通过封装一个延时执行的方法 timeout 实现的:

const timeout = (promise, ms) => new Promise((resolve, reject) => {
    setTimeout(() => {
        promise.then(resolve, reject);
    }, ms);
});

ms参数是延时时间,promise接受一个Promise对象。

解决多次循环异步提取图片文字的业务,可以看下边的代码:其中orcModule.execOrcByImgPath方法(异步的),通过 async/await 来控制同步编程,内部多次异步循环拿到所有的循环结果。最后 pdfPath 方法统一返回结果:

const pdfOcr = pdfPath => new Promise((resolve, reject) => {
    let wordstr = "";
    (async () => {
        let imagePaths = await pdfModule.convertFile(pdfPath, { totalPageSize: 4 });
        const { length } = imagePaths;
        let count = -1;
        console.log(imagePaths);
        while (++count < length) {
            try {
                // 延时2秒执行原因:免费版百度OCR 接口有qps限制
                let result = await timeout(orcModule.execOrcByImgPath(imagePaths[count]), 2000);
                if (!result.error_code) {
                    const { words_result } = result;
                    for (let row of words_result) {
                        wordstr += row.words + '\n';
                    }
                }
            } catch (error) {
                reject(error);
            }

        }
        resolve(wordstr);
    })();
});

三、axios简单包装

做项目的时候,我们会直接采用一些被人封装好的module去实现接口请求,比如 axios 就不错,也比较流行。我们在使用的过程还可以进一步包装,如下代码简单Promise包装post请求,然后配合 async/await 来使用

const BASE_URL = "http://192.168.100.121:17777/api/";

// Add a request interceptor
axios.interceptors.request.use(
  config => {
    config.timeout = 10000;
    return config;
  },
  error => {
    // Do something with response error
    return Promise.reject(error);
  }
);
// Add a response interceptor
axios.interceptors.response.use(
  response => {
    // Do something with response data
    // TODO:hide loading
    return response;
  },
  error => {
    debugger;
    // Do something with response error
    return Promise.reject(error);
  }
);
/* 
const baseInstance = axios.create({
    baseURL: BASE_URL,
    timeout: 2000,
    headers: { 'X-Custom-Header': 'foobar' }
}); */

/* post 请求统一封装 */
function post(backend, params) {
  const URL = BASE_URL + backend;
  return new Promise((resolve, reject) => {
    axios
      .post(URL, params)
      .then(response => {
        const data = response.data;
        return resolve(data);
      })
      .catch(error => {
        console.log(error);
        return reject(error);
      });
  });
}

/* 测试使用post */
// async/await 来实现异步同步编程
async function test() {
  const result = await post("baseConfig/findAreaList", [
    {
      parentCode: "000000000000"
    }
  ]);
  console.log(result.content);
}

test();

总结

理解该段代码需要熟悉 async/await 语法糖的使用。请求超时该使用场景不多,主要场景是在对请求的一些封装处理,等待接口响应时间如果超时,重新发请求的情况。代码来源于sindresorhus/ky。另外OCR项目源码:giscafer/easyocr