Javascript 获取,失败时重试。
介绍
让我们思考一下
简要介绍 fetch
那么如何实现呢?
结论
由于某种原因,之前的帖子没有出现在我的个人资料中,因此重新发布了此帖子。
最近,我遇到了网络随机通断的情况。由于这会影响测试结果的一致性,我决定实现一个在网络故障时最多fetch_retry
重试 100 次的功能。fetch
n
介绍
JavaScript 中的Fetch非常棒。我希望你会同意,它提供了一个简单但足够强大的接口来处理我们的 AJAX 请求。
然而,网络并不总是像我们期望的那样工作,它可能会随机失败。为了解决这个问题,让我们实现一个函数,fetch_retry(url, options, n)
该函数会在失败时fetch(url, options)
重试最多 100n
次。从而提高成功的几率。
让我们思考一下
重试听起来像个循环。我们为什么不写一个 for/while 循环来实现呢?或许可以像下面这样?
function fetch_retry(url, options, n) {
for(let i = 0; i < n; i++){
fetch(url, options);
if(succeed) return result;
}
}
不! Fetch 是一个异步函数,这意味着程序不会等待结果就继续执行!n
无论之前的调用是否成功,fetch 都会被同时调用(某种程度上来说)。
这不是我们想要的。这不是失败重试,而是n
同时获取时间!(话虽如此,如果写得正确,它也可以增加成功的几率。也许可以用类似这样的东西Promsie.any
?虽然我不是 Bluebird 的忠实粉丝。我认为原生的 Promise 已经足够好了。)
如果您不了解异步函数和Javascript,请在继续阅读之前观看由Jessica Kerr制作的Promise
精彩视频!
简要介绍 fetch
所以fetch
返回一个Promise
。我们通常这样称呼它。
fetch(url, { method: 'GET' }).then(res => console.log('done'));
console.log('fetching...');
如果你理解Promise
正确的话,你应该期望结果是:
fetching...
done
如果网络由于某种原因出现故障,Promise
则会拒绝,我们可以按如下方式捕获错误:
fetch(url, { method: 'GET' }).catch(err => /* ... */);
那么如何实现呢?
做什么fetch_retry
?
我们首先思考一下这个函数到底想做什么fetch_retry
。我们知道它必须以某种方式调用 fetch,所以就把它写下来。
function fetch_retry(url, options, n) {
fetch(url, options)
.then(function(result) {
/* on success */
}).catch(function(error) {
/* on failure */
})
}
现在显然fetch_retry
必须是一个异步函数,因为我们无法真正从异步函数中定义一个同步函数。(或者我们可以吗?启发我。)
定义:这意味着如果任何一次尝试成功,fetch_retry
则返回一个解析,如果所有尝试都失败,则返回一个拒绝。Promise
n
n
那么现在让我们返回Promise
。
function fetch_retry(url, options, n) {
return new Promise(function(resolve, reject) { // <--- we know it is asynchronous, so just return a promise first!
fetch(url, options)
.then(function(result) {
/* on success */
}).catch(function(error) {
/* on failure */
})
});
}
万一fetch
成功了呢?
因此,如果获取成功,我们显然可以通过调用该resolve
函数来解析返回的 Promise。因此代码如下:
function fetch_retry(url, options, n) {
return new Promise(function(resolve, reject) {
fetch(url, options)
.then(function(result) {
/* on success */
resolve(result); // <--- yeah! we are done!
}).catch(function(error) {
/* on failure */
})
});
}
如果fetch
失败了怎么办?
失败时该怎么办?由于我们之前讨论过的异步特性,在这里使用 for/while 循环实际上没什么用。但有一件事我们可以做到 for/while 循环能做到的。是不是想起来了?没错!递归!
执行递归时我的两个经验法则:
- 不要用递归的方式思考。不要尝试用递归的方式追踪你的代码。
- 凭着信念的飞跃,假设你定义的递归函数有效。
这两点本质上是一样的!如果你有信心,你就不会在代码中递归地思考了。
好的,让我们尝试一下信念的飞跃,并假设fetch_retry
它会神奇地起作用。
如果它有效,那么在中on failure
,如果我们调用会发生什么fetch_retry(url, options, n - 1)
?
function fetch_retry(url, options, n) {
return new Promise(function(resolve, reject) {
fetch(url, options)
.then(function(result) {
/* on success */
resolve(result);
})
.catch(function(error) {
/* on failure */
fetch_retry(url, options, n - 1) // <--- leap of faith, this will just work magically! Don't worry!
.then(/* one of the remaining (n - 1) fetch succeed */)
.catch(/* remaining (n - 1) fetch failed */);
})
});
}
fetch_retry(url, options, n - 1)
只需凭着信念的飞跃就能奇迹般地发挥作用,并返回一个Promise
,根据我们之前讨论过的定义,如果任何尝试(在n - 1
尝试中)成功则解析,如果所有n - 1
尝试都失败则拒绝。
那么现在,递归调用之后我们该做什么呢?注意,因为fetch_retry(url, options, n - 1)
会神奇地工作,这意味着我们n
此时已经完成了所有的获取操作。在这种on failure
情况下,如果fetch_retry(url, options, n - 1)
解析成功,就解析;如果拒绝,就拒绝。
function fetch_retry(url, options, n) {
return new Promise(function(resolve, reject) {
fetch(url, options)
.then(function(result) {
/* on success */
resolve(result);
})
.catch(function(error) {
fetch_retry(url, options, n - 1)
.then(resolve) // <--- simply resolve
.catch(reject); // <--- simply reject
})
});
}
太棒了!快完成了!我们知道这个递归调用需要一个起始条件。在考虑起始条件时,我们会查看函数参数,并确定在什么情况下可以立即得出结果。
答案是当n === 1
和fetch
失败时。在这种情况下,我们可以直接拒绝 的错误fetch
,而无需fetch_retry
递归调用。
function fetch_retry(url, options, n) {
return new Promise(function(resolve, reject) {
fetch(url, options)
.then(function(result) {
/* on success */
resolve(result);
})
.catch(function(error) {
if (n === 1) return reject(error); // <--- base case!
fetch_retry(url, options, n - 1)
.then(resolve)
.catch(reject);
})
});
}
清理干净
冗余功能
在我们的“成功时”函数中,我们只是调用了resolve(result)
。所以这个函数实例是多余的,我们可以直接将其用作resolve
“成功时”函数。因此代码将变为:
function fetch_retry(url, options, n) {
return new Promise(function(resolve, reject) {
fetch(url, options).then(resolve) // <--- Much cleaner!
.catch(function(error) {
if (n === 1) return reject(error);
fetch_retry(url, options, n - 1)
.then(resolve)
.catch(reject);
})
});
}
多余的承诺
现在我们在这里做的另一件愚蠢的事情是这一行:
fetch_retry(url, options, n - 1).then(resolve).catch(reject)
你知道问题是什么吗?
让我来解释一下,我们基本上是这样做的:
new Promise(function(resolve, reject) {
fetch_retry(url, options, n - 1).then(resolve).catch(reject)
});
所以在这种情况下,这个新的 Promise 是多余的,因为如果fetch_retry
resolve 则解析,如果fetch_retry
rejection 则拒绝。所以它的行为基本上和 的行为完全一样fetch_retry
!
因此,上述代码在语义上与单独执行基本相同fetch_retry
。
fetch_retry(url, options, n - 1)
// sementically the same thing as the following
new Promise(function(resolve, reject) {
fetch_retry(url, options, n - 1).then(resolve).catch(reject)
});
为了简化代码,还需要掌握一些知识。我们可以promise.then
用下面的方式链接 s 。因为它promise.then
也返回一个 Promise!
Promise.resolve(3).then(function(i) {
return i * 2;
}).then(function(i) {
return i === 6; // this will be true
});
如你所见,我们可以将处理后的值传递给下一个,then
依此类推。如果值为 a Promise
,则下一个then
将接收返回的Promise
解析结果。如下所示:
Promise.resolve(3).then(function(i) {
return i * 2;
}).then(function(i) {
return Promise.resolve(i * 2); // also work!
}).then(function(i) {
return i === 12; // this is true! i is not a Promise!
};
catch
同样的想法也可以应用!感谢Corentin的鼓励!这意味着我们甚至可以在 Promise 被拒绝时,将其解决。以下是一个例子:
Promise.resolve(3).then(function(i) {
throw "something's not right";
}).catch(function(i) {
return i
}).then(function(i) {
return i === "something's not right";
};
那么,我们该如何利用这些知识进行清理呢?我们的代码似乎更复杂。
function fetch_retry(url, options, n) {
return new Promise(function(resolve, reject) {
fetch(url, options).then(resolve)
.catch(function(error) {
if (n === 1) return reject(error);
fetch_retry(url, options, n - 1)
.then(resolve) // <--- we try to remove this
.catch(reject); // <--- and this
})
});
}
好吧,我们可以用 ! 返回的 Promise 来解析返回的 Promise fetch_retry
,而不是 。fetch_retry(...).then(resolve).catch(reject)
我们可以这样做resolve(fetch_retry(...))
! 因此代码变为:
function fetch_retry(url, options, n) {
return new Promise(function(resolve, reject) {
fetch(url, options).then(resolve)
.catch(function(error) {
if (n === 1) return reject(error);
resolve(fetch_retry(url, options, n - 1)); // <--- clean, isn't it?
})
});
}
现在我们可以更进一步,Promise
通过解决中的承诺来消除明确的创建catch
。
function fetch_retry(url, options, n) {
return fetch(url, options).catch(function(error) {
if (n === 1) throw error;
return fetch_retry(url, options, n - 1);
});
}
引用MDN中的一些内容,并对其进行了一些调整,使其更通俗易懂:
如果处理程序抛出错误或返回本身已被拒绝的 Promise,则 catch(handler) 返回的 Promise 将被拒绝;否则,它将被解决。
ES6
我可以预料到,一些 JS 大佬会因为我不使用箭头函数而讨厌我。我不会为那些不习惯使用箭头函数的人使用。这是用箭头函数编写的 ES6 版本,我就不多解释了。
const fetch_retry = (url, options, n) => fetch(url, options).catch(function(error) {
if (n === 1) throw error;
return fetch_retry(url, options, n - 1);
});
快乐的?
ES7
是啊,Promise
ES7 的 async/await 版本发布后,它很快就会过时。所以这里有一个 async/await 版本:
const fetch_retry = async (url, options, n) => {
try {
return await fetch(url, options)
} catch(err) {
if (n === 1) throw err;
return await fetch_retry(url, options, n - 1);
}
};
看起来整洁多了,对吧?
事实上,我们不必在 ES7 中使用递归,我们可以使用简单的 for 循环来定义它。
const fetch_retry = async (url, options, n) => {
let error;
for (let i = 0; i < n; i++) {
try {
return await fetch(url, options);
} catch (err) {
error = err;
}
}
throw error;
};
// or (tell me which one u like better, I can't decide.)
const fetch_retry = async (url, options, n) => {
for (let i = 0; i < n; i++) {
try {
return await fetch(url, options);
} catch (err) {
const isLastAttempt = i + 1 === n;
if (isLastAttempt) throw err;
}
}
};
结论
总结一下,我们研究了同一个函数的四个不同版本。其中三个是递归的,只是编写风格和风格不同。最后一个使用了 for 循环。让我们回顾一下:
原始版本
function fetch_retry(url, options, n) {
return fetch(url, options).catch(function(error) {
if (n === 1) throw error;
return fetch_retry(url, options, n - 1);
});
}
ES6
const fetch_retry = (url, options, n) => fetch(url, options).catch(function(error) {
if (n === 1) throw error;
return fetch_retry(url, options, n - 1);
});
ES7 async/await 递归
这是我最喜欢的。
const fetch_retry = async (url, options, n) => {
try {
return await fetch(url, options)
} catch(err) {
if (n === 1) throw err;
return await fetch_retry(url, options, n - 1);
}
};
ES7 async/await for 循环
const fetch_retry = async (url, options, n) => {
let error;
for (let i = 0; i < n; i++) {
try {
return await fetch(url, options);
} catch (err) {
error = err;
}
}
throw error;
};
// or (tell me which one u like better, I can't decide.)
const fetch_retry = async (url, options, n) => {
for (let i = 0; i < n; i++) {
try {
return await fetch(url, options);
} catch (err) {
const isLastAttempt = i + 1 === n;
if (isLastAttempt) throw err;
}
}
};
在评论中告诉我你的反馈!:D
文章来源:https://dev.to/ycmjason/javascript-fetch-retry-upon-failure-3p6g