@@ -13,7 +13,8 @@ const DEFAULT_OPTIONS = {
1313 baseURI : null , // {String|null} a base URI to resolve relative URIs.
1414 ignore : [ ] , // {Array<String>} URIs to be skipped from availability checks.
1515 preferGET : [ ] , // {Array<String>} origins to prefer GET over HEAD.
16- concurrency : 8 // concurrency count of linting link
16+ concurrency : 8 , // {number} Concurrency count of linting link,
17+ retry : 3 , // {number} Max retry count
1718} ;
1819
1920// Adopted from http://stackoverflow.com/a/3809435/951517
@@ -69,13 +70,33 @@ function isIgnored(uri, ignore = []) {
6970 return ignore . some ( ( pattern ) => minimatch ( uri , pattern ) ) ;
7071}
7172
73+ /**
74+ * wait for ms and resolve the promise
75+ * @param ms
76+ * @returns {Promise<any> }
77+ */
78+ function waitTimeMs ( ms ) {
79+ return new Promise ( resolve => {
80+ setTimeout ( resolve , ms ) ;
81+ } ) ;
82+ }
83+
7284/**
7385 * Checks if a given URI is alive or not.
86+ *
87+ * Normally, this method following strategiry about retry
88+ *
89+ * 1. Head
90+ * 2. Get
91+ * 3. Get
92+ *
7493 * @param {string } uri
7594 * @param {string } method
95+ * @param {number } maxRetryCount
96+ * @param {number } currentRetryCount
7697 * @return {{ ok: boolean, redirect?: string, message: string } }
7798 */
78- async function isAliveURI ( uri , method = 'HEAD' ) {
99+ async function isAliveURI ( uri , method = 'HEAD' , maxRetryCount = 3 , currentRetryCount = 0 ) {
79100 const { host } = URL . parse ( uri ) ;
80101 const opts = {
81102 method,
@@ -115,10 +136,17 @@ async function isAliveURI(uri, method = 'HEAD') {
115136 } ;
116137 }
117138
118- if ( ! res . ok && method === 'HEAD' ) {
119- return isAliveURI ( uri , 'GET' ) ;
139+ if ( ! res . ok && method === 'HEAD' && currentRetryCount < maxRetryCount ) {
140+ return isAliveURI ( uri , 'GET' , maxRetryCount , currentRetryCount + 1 ) ;
120141 }
121142
143+ // try to fetch again if not reach max retry count
144+ if ( currentRetryCount < maxRetryCount ) {
145+ // exponential retry
146+ // 0ms -> 100ms -> 200ms -> 400ms -> 800ms ...
147+ await waitTimeMs ( ( currentRetryCount ** 2 ) * 100 ) ;
148+ return isAliveURI ( uri , 'GET' , maxRetryCount , currentRetryCount + 1 ) ;
149+ }
122150 return {
123151 ok : res . ok ,
124152 message : `${ res . status } ${ res . statusText } ` ,
@@ -127,8 +155,8 @@ async function isAliveURI(uri, method = 'HEAD') {
127155 // Retry with `GET` method if the request failed
128156 // as some servers don't accept `HEAD` requests but are OK with `GET` requests.
129157 // https://github.com/textlint-rule/textlint-rule-no-dead-link/pull/86
130- if ( method === 'HEAD' ) {
131- return isAliveURI ( uri , 'GET' ) ;
158+ if ( method === 'HEAD' && currentRetryCount < maxRetryCount ) {
159+ return isAliveURI ( uri , 'GET' , maxRetryCount , currentRetryCount + 1 ) ;
132160 }
133161
134162 return {
@@ -169,8 +197,9 @@ function reporter(context, options = {}) {
169197 * @param {TextLintNode } node TextLintNode the URI belongs to.
170198 * @param {string } uri a URI string to be linted.
171199 * @param {number } index column number the URI is located at.
200+ * @param {number } maxRetryCount retry count of linting
172201 */
173- const lint = async ( { node, uri, index } ) => {
202+ const lint = async ( { node, uri, index } , maxRetryCount ) => {
174203 if ( isIgnored ( uri , opts . ignore ) ) {
175204 return ;
176205 }
@@ -209,12 +238,11 @@ function reporter(context, options = {}) {
209238
210239 const result = isLocal ( uri )
211240 ? await isAliveLocalFile ( uri )
212- : await memorizedIsAliveURI ( uri , method ) ;
241+ : await memorizedIsAliveURI ( uri , method , maxRetryCount ) ;
213242 const { ok, redirected, redirectTo, message } = result ;
214243
215244 if ( ! ok ) {
216245 const lintMessage = `${ uri } is dead. (${ message } )` ;
217-
218246 report ( node , new RuleError ( lintMessage , { index } ) ) ;
219247 } else if ( redirected && ! opts . ignoreRedirects ) {
220248 const lintMessage = `${ uri } is redirected to ${ redirectTo } . (${ message } )` ;
@@ -276,9 +304,9 @@ function reporter(context, options = {}) {
276304 } ,
277305
278306 [ `${ context . Syntax . Document } :exit` ] ( ) {
279- const linkTasks = URIs . map ( ( item ) => ( ) => lint ( item ) ) ;
307+ const linkTasks = URIs . map ( ( item ) => ( ) => lint ( item , opts . retry ) ) ;
280308 return pAll ( linkTasks , {
281- concurrency : opts . concurrency
309+ concurrency : opts . concurrency ,
282310 } ) ;
283311 } ,
284312 } ;
0 commit comments