读懂文章需要掌握异步控制的两个技术点:
- Promise
- async/await
下边单独分开两个问题代码实践
一、超时问题需求背景
某接口请求响应超过指定时间(比如3秒)后,提示超时或者重新请求。或者走其他方式获取数据。
代码实现
提示:利用了Promise状态一旦发生变化,就无法再改变的特点
const delay = ms => new Promise(resolve => setTimeout(resolve, ms));
const timeout = (promise, ms) => new Promise((resolve, reject) => {
promise.then(resolve, reject);
(async () => {
await delay(ms);
reject(new Error('delay error'));
})();
})
测试一下
我们来测试一下
/*
* 规定某时间超时后的行为控制
*/
const delay = ms =>
new Promise(resolve => {
console.log("delay function run setTimeout function");
setTimeout(() => {
console.log("delay function setTimeout callback inner");
resolve();
}, ms);
});
const timeout = (promise, ms) =>
new Promise((resolve, reject) => {
console.log(1);
promise.then(resolve, reject);
console.log(2);
(async () => {
console.log("delay before");
await delay(ms);
console.log("delay after");
reject("delay error");
})();
});
// 模拟异步接口请求
const getInfo = () =>
new Promise((resolve, reject) => {
setTimeout(() => {
console.log("getinfo resolve");
resolve("result data");
}, 4000);
});
async function testIIF(ms) {
let result = await timeout(getInfo(), ms);
return result;
}
// 可以修改超时时间为3000,参考不同运行结果
testIIF(4000)
.then(res => {
console.log("接口请求成功=", res);
})
.catch(e => {
console.log("接口超时:", JSON.stringify(e));
});
在线运行: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