มาเขียน Promise.all ให้สามารถทำ retry logic ได้

NV4RE
True e-Logistics
3 min readMay 7, 2019

--

เคยไหมที่จะยิง get หรือ post ข้อมูลมาจาก http แต่อยากทำพร้อมๆกันที่ละหลายอัน แต่ site ที่เรายิงไปกลับล่มซะงั้น, หากเราใช้ Promise.all ก็จะ throw เฉพาะ error อันแรกเท่านั้น ไม่รู้ว่าอันไหนสำเร็จอันไหนไม่สำเร็จ

// ~50% of getData will fail 
const postData = (url) => new Promise((resolve, reject) => {
setTimeout(() => {
if (Math.random() > 0.5)
return reject({
url,
statusCode: 500,
data: null
})
resolve({
url,
statusCode: 200,
data: `Some data from ${url}`
})
}, 500)
})
const urlList = [
'site1.com',
'site2.com',
'site3.com',
'site4.com',
'site5.com',
'site6.com',
'site7.com',
'site8.com',
'site9.com',
'site10.com'
]
Promise.all(urlList.map(postData))
.then(console.log)
.catch(console.error)
// {url: "site4.com", statusCode: 500, data: null}

จากตัวอย่างข้างบน เราได้ยิงไปที่ 10 sites โดยจะสุ่มให้ request ที่ยิงไปส่วนหนึ่งล้มเหลว

ลองมาเปลี่ยน code เล็กน้อยเพื่อให้เราสามารถบอกได้ว่า request ไหนสำเร็จและไม่สำเร็จบ้างโดยไม่ต้อง throw error ออกมา

const postData = (url) => new Promise((resolve, reject) => {
setTimeout(() => {
if (Math.random() > 0.5)
return reject({
url,
statusCode: 500,
data: null
})
resolve({
url,
statusCode: 200,
data: `Some data from ${url}`
})
}, 500)
})
const urlList = [
'site1.com',
'site2.com',
'site3.com',
'site4.com',
'site5.com',
'site6.com',
'site7.com',
'site8.com',
'site9.com',
'site10.com'
]
Promise.all(
urlList.map(url =>
postData(url)
.then(data => ({ data, success: true }))
.catch(data => ({ data, success: false }))
)
)
.then(console.log)
.catch(console.error);
// [
// {
// "data": {
// "url": "site1.com",
// "statusCode": 200,
// "data": "Some data from site1.com"
// },
// "success": true
// },
// {
// "data": {
// "url": "site2.com",
// "statusCode": 500,
// "data": null
// },
// "success": false
// },
// {
// "data": {
// "url": "site3.com",
// "statusCode": 200,
// "data": "Some data from site3.com"
// },
// "success": true
// },
// ...7 more
// ]

ตอนนี้เราก็สามารถแยก request ที่สำเร็จกับไม่สำเร็จได้แล้วด้วย field “success” ว่าเป็น true หรือเปล่า

เอาละตอนนี้ก็เพิ่มส่วนของการทำ retry เข้าไป

const postData = url =>
new Promise((resolve, reject) => {
setTimeout(() => {
if (Math.random() > 0.5)
return reject({
url,
statusCode: 500,
data: null
});
resolve({
url,
statusCode: 200,
data: `Some data from ${url}`
});
}, 500);
});
const urlList = [
"site1.com",
"site2.com",
"site3.com",
"site4.com",
"site5.com",
"site6.com",
"site7.com",
"site8.com",
"site9.com",
"site10.com"
];
const batchPostDataWithRetry = async (urls = []) => {
const responses = await Promise.all(
urls.map(url =>
postData(url)
.then(data => ({ data, success: true }))
.catch(data => ({ data, success: false }))
)
);
const failedResponses = responses
.filter(({ success }) => !success);
const failedUrls = failedResponses
.map(({ data: { url } }) => url);
if (failedResponses.length) {
return [
...responses.filter(({ success }) => success),
...(await batchPostDataWithRetry(failedUrls))
];
}
return responses;
};batchPostDataWithRetry(urlList).then(console.log);// [
// {
// "data": {
// "url": "site4.com",
// "statusCode": 200,
// "data": "Some data from site4.com"
// },
// "success": true
// },
// {
// "data": {
// "url": "site7.com",
// "statusCode": 200,
// "data": "Some data from site7.com"
// },
// "success": true
// },
// {
// "data": {
// "url": "site8.com",
// "statusCode": 200,
// "data": "Some data from site8.com"
// },
// "success": true
// },
// {
// "data": {
// "url": "site10.com",
// "statusCode": 200,
// "data": "Some data from site10.com"
// },
// "success": true
// },
// {
// "data": {
// "url": "site2.com",
// "statusCode": 200,
// "data": "Some data from site2.com"
// },
// "success": true
// },
// {
// "data": {
// "url": "site3.com",
// "statusCode": 200,
// "data": "Some data from site3.com"
// },
// "success": true
// },
// {
// "data": {
// "url": "site6.com",
// "statusCode": 200,
// "data": "Some data from site6.com"
// },
// "success": true
// },
// {
// "data": {
// "url": "site1.com",
// "statusCode": 200,
// "data": "Some data from site1.com"
// },
// "success": true
// },
// {
// "data": {
// "url": "site5.com",
// "statusCode": 200,
// "data": "Some data from site5.com"
// },
// "success": true
// },
// {
// "data": {
// "url": "site9.com",
// "statusCode": 200,
// "data": "Some data from site9.com"
// },
// "success": true
// }
// ]

--

--