
读懂文章需要掌握异步控制的两个技术点:
- 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
