2015-03-12 22 views
16

Sto scrivendo un Javascript Promise che trova l'URL di reindirizzamento finale di un collegamento.Promessa ricorsiva in javascript

Quello che sto facendo è una richiesta HEAD in un Promise utilizzando un XMLHttpRequest. Quindi, al caricamento, controlla lo stato HTTP per qualcosa nell'intervallo 300 o se ha un valore responseURL collegato all'oggetto e che l'URL è diverso da quello con cui è stato eseguito.

Se nessuna di queste condizioni è vera, I resolve(url). In caso contrario, chiamerò in modo ricorsivo getRedirectUrl() nell'URL della risposta e resolve().

Ecco il mio codice:

function getRedirectUrl(url, maxRedirects) { 
    maxRedirects = maxRedirects || 0; 
    if (maxRedirects > 10) { 
     throw new Error("Redirected too many times."); 
    } 

    var xhr = new XMLHttpRequest(); 
    var p = new Promise(function (resolve) { 
     xhr.onload = function() { 
      var redirectsTo; 
      if (this.status < 400 && this.status >= 300) { 
       redirectsTo = this.getResponseHeader("Location"); 
      } else if (this.responseURL && this.responseURL != url) { 
       redirectsTo = this.responseURL; 
      } 

      if (redirectsTo) { 
       // check that redirect address doesn't redirect again 
       // **problem line** 
       p.then(function() { self.getRedirectUrl(redirectsTo, maxRedirects + 1); }); 
       resolve(); 
      } else { 
       resolve(url); 
      } 
     } 

     xhr.open('HEAD', url, true); 
     xhr.send(); 
    }); 

    return p; 
} 

Quindi per utilizzare questa funzione che faccio qualcosa di simile:

getRedirectUrl(myUrl).then(function (url) { ... }); 

Il problema è che resolve(); in getRedirectUrl chiamerà l'then() dalla funzione chiamante prima di chiamare la chiamata ricorsiva getRedirectUrl e, a quel punto, l'URL è undefined.

Ho provato, piuttosto che p.then(...getRedirectUrl...) facendo return self.getRedirectUrl(...) ma questo non risolverà mai.

La mia ipotesi è che il modello che sto usando (che sostanzialmente ho inventato al volo) non è giusto, del tutto.

+2

'p.then (...)' con una funzione che non produce effetti secondari osservabili e 'return's nothing is nothingless. – zerkms

risposta

23

Il problema è che la promessa di ritorno da getRedirectUrl() deve includere l'intera catena di logica per ottenere l'URL. Stai solo restituendo una promessa per la prima richiesta. Lo .then() che stai utilizzando nel bel mezzo della tua funzione non sta facendo nulla.

Per risolvere questo problema:

Creare una promessa che si risolve in redirectUrl per un redirect, o niente altrimenti:

var p = new Promise(function (resolve) { 
    var xhr = new XMLHttpRequest(); 

    xhr.onload = function() { 
     var redirectsTo; 

     if (xhr.status < 400 && xhr.status >= 300) { 
      redirectsTo = xhr.getResponseHeader("Location"); 
     } else if (xhr.responseURL && xhr.responseURL != url) { 
      redirectsTo = xhr.responseURL; 
     } 

     resolve(redirectsTo); 
    }; 

    xhr.open('HEAD', url, true); 
    xhr.send(); 
}); 

Usa .then() su che di restituire la chiamata ricorsiva, o no, come necessario:

return p.then(function (redirectsTo) { 
    return redirectsTo 
     ? getRedirectUrl(redirectsTo, redirectCount+ 1) 
     : url; 
}); 

soluzione completa:

function getRedirectUrl(url, redirectCount) { 
    redirectCount = redirectCount || 0; 
    if (redirectCount > 10) { 
     throw new Error("Redirected too many times."); 
    } 

    return new Promise(function (resolve) { 
     var xhr = new XMLHttpRequest(); 

     xhr.onload = function() { 
      var redirectsTo; 

      if (xhr.status < 400 && xhr.status >= 300) { 
       redirectsTo = xhr.getResponseHeader("Location"); 
      } else if (xhr.responseURL && xhr.responseURL != url) { 
       redirectsTo = xhr.responseURL; 
      } 

      resolve(redirectsTo); 
     }; 

     xhr.open('HEAD', url, true); 
     xhr.send(); 
    }) 
    .then(function (redirectsTo) { 
     return redirectsTo 
      ? getRedirectUrl(redirectsTo, redirectCount+ 1) 
      : url; 
    }); 
} 
+1

Sono confuso! Non vedo come succede qualcosa qui. Se chiamo getRedirectUrl() restituisce una promessa e quella promessa ha una funzione resolve() che fa qualcosa. E quando questa roba è terminata, potrebbe ricorrere a seguire un reindirizzamento. Ma! Come fa il primo xhr.open(); xhr.send() mai accade se l'unico posto in cui si trova è nella funzione Promise resolve()? Sono ancora nuovo alle promesse e JavaScript quindi potrebbe mancare qualcosa qui. – theWebalyst

+0

@theWebalyst La funzione con 'xhr.open()' e 'xhr.send()' viene chiamata prima che 'getRedirectUrl()' ritorni. La funzione passata nel costruttore 'Promise' è chiamata immediatamente dal costruttore' Promise'. – JLRishe

+0

@theWebalyst Il dettaglio qui è che la funzione 'resolve' è in realtà un argomento della funzione di gestione passata al costruttore' Promise'.L'errore nel tuo modo di pensare che causa la confusione è * che la promessa ha una funzione resolve() che fa qualcosa. *. In realtà, la funzione di gestione fa alcune cose e chiama la funzione 'resolve' che è stata passata quando è stata eseguita. Potresti aggiungere un secondo argomento chiamato 'reject' e chiamarlo invece di' throw'ing quando ci sono troppi reindirizzamenti. L'effetto è lo stesso. –