New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
amp-next-page: Fetch failure should be a user error #22293
Changes from 4 commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -20,7 +20,7 @@ import { | |
PositionObserverFidelity, | ||
} from '../../../src/service/position-observer/position-observer-worker'; | ||
import {Services} from '../../../src/services'; | ||
import {dev, userAssert} from '../../../src/log'; | ||
import {dev, user, userAssert} from '../../../src/log'; | ||
import {dict} from '../../../src/utils/object'; | ||
import {getAmpdoc, getServiceForDoc} from '../../../src/service'; | ||
import { | ||
|
@@ -237,7 +237,8 @@ export class NextPageService { | |
appendNextArticle_() { | ||
if (this.nextArticle_ < this.config_.pages.length) { | ||
const next = this.config_.pages[this.nextArticle_]; | ||
const documentRef = createDocumentRef(next.ampUrl); | ||
const {ampUrl} = next; | ||
const documentRef = createDocumentRef(ampUrl); | ||
this.documentRefs_.push(documentRef); | ||
|
||
const container = this.win_.document.createElement('div'); | ||
|
@@ -269,53 +270,56 @@ export class NextPageService { | |
} | ||
|
||
this.nextArticle_++; | ||
this.xhr_.fetch(next.ampUrl, {ampCors: false}) | ||
.then(response => { | ||
// Update AMP URL in case we were redirected. | ||
documentRef.ampUrl = response.url; | ||
const url = this.urlService_.parse(response.url); | ||
userAssert(url.origin === this.origin_, | ||
'ampUrl resolved to a different origin from the origin of the ' | ||
+ 'current document'); | ||
return response.text(); | ||
}) | ||
.then(html => { | ||
const doc = | ||
this.win_.document.implementation.createHTMLDocument(''); | ||
doc.open(); | ||
doc.write(html); | ||
doc.close(); | ||
return doc; | ||
}) | ||
.then(doc => new Promise((resolve, reject) => { | ||
if (documentRef.cancelled) { | ||
// User has reached the end of the document already, don't render. | ||
resolve(); | ||
return; | ||
} | ||
const fetchPromise = this.xhr_.fetch(ampUrl, {ampCors: false}).then(r => { | ||
// Update AMP URL in case we were redirected. | ||
documentRef.ampUrl = r.url; | ||
const url = this.urlService_.parse(r.url); | ||
userAssert(url.origin === this.origin_, | ||
'ampUrl resolved to a different origin from the origin of the ' | ||
+ 'current document'); | ||
return r.text(); | ||
}, e => { | ||
user().error(TAG, 'Failed to fetch: %s', ampUrl, e); | ||
}); | ||
// Once next page's HTML at `ampUrl` is fetched, inject it into a new doc. | ||
fetchPromise.then(html => { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not sure this does what you want. The catch above will cause you to enter this this.xhr_.fetch(...)
.then(r => { ...}, e => {...})
.then(html => [...}); Since this function is quite long already, I think this could be fixed and clarified by breaking out the attachment of the html part into a separate function. For example: const fetchPromise = this.xhr_.fetch(...);
fetchPromise.catch(e => {...});
fetchPromise
.then(r => {...})
.then(html => {
return appendArticleFromHtml(html);
}); As-is, it is pretty hard to follow the flow of The new code seems like it should attach a doc with the content 'undefined', which I don't think is the right thing to do for a failure. The previous code looks to behave correctly by skipping all the attaching steps in the in the case of the fetch failure by letting the error fall through until the failed to fetch dev error. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good catch, added an early return. 👍 Yea I couldn't resist the light refactoring because of readability. Looked into a "appendArticleWithHtml" helper but it'd need ~5 params so it doesn't feel like a readability win. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Added a suggestion on how to refactor this above. You don't really need to pass too many arguments, if you separate out concerns here. There are also a few minor things to clean up. For example, there is no need to wrap the mutate element with a try/catch, because the catch only is only ever logging an error and recovering into a non-failed state (assuming the if (documentRef.cancelled) {
return;
}
...
return this.resources_.mutateElement(container, () => {
try {
...
} catch (e) {
dev().error(...);
}
}); There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I would prefer either this is code is refactored more broadly, or the minimum change needed is made in this PR. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Minimum change it is. :) |
||
if (!html) { | ||
return; | ||
} | ||
const doc = this.win_.document.implementation.createHTMLDocument(''); | ||
doc.open(); | ||
doc.write(html); | ||
doc.close(); | ||
|
||
return new Promise((resolve, reject) => { | ||
if (documentRef.cancelled) { | ||
// User has reached the end of the document already, don't render. | ||
resolve(); | ||
return; | ||
} | ||
if (documentRef.recUnit.isObserving) { | ||
this.positionObserver_.unobserve(articleLinks); | ||
documentRef.recUnit.isObserving = true; | ||
} | ||
this.resources_.mutateElement(container, () => { | ||
try { | ||
const amp = this.attachShadowDoc_(shadowRoot, doc); | ||
documentRef.amp = amp; | ||
|
||
if (documentRef.recUnit.isObserving) { | ||
this.positionObserver_.unobserve(articleLinks); | ||
documentRef.recUnit.isObserving = true; | ||
toggle(dev().assertElement(documentRef.recUnit.el), false); | ||
this.documentQueued_ = false; | ||
resolve(); | ||
} catch (e) { | ||
reject(e); | ||
} | ||
this.resources_.mutateElement(container, () => { | ||
try { | ||
const amp = this.attachShadowDoc_(shadowRoot, doc); | ||
documentRef.amp = amp; | ||
|
||
toggle(dev().assertElement(documentRef.recUnit.el), false); | ||
this.documentQueued_ = false; | ||
resolve(); | ||
} catch (e) { | ||
reject(e); | ||
} | ||
}); | ||
}), | ||
e => dev().error(TAG, 'failed to fetch %s', next.ampUrl, e)) | ||
.catch(e => dev().error(TAG, | ||
'failed to attach shadow document for %s', next.ampUrl, e)) | ||
// The new page may be short and the next may already need fetching. | ||
.then(() => this.scrollHandler_()); | ||
}); | ||
}); | ||
}).catch(e => { | ||
dev().error(TAG, 'Failed to attach shadow document: %s', ampUrl, e); | ||
}).then(() => { | ||
// The new page may be short and the next may already need fetching. | ||
this.scrollHandler_(); | ||
}); | ||
} | ||
} | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
appendNextArticleHtml
needs to pass indocumentRef
andnext.ampUrl
too.Personally I don't find this more readable e.g. both "fetch" and "append" mutate
documentRef
, so readers need to jump between methods to understand what's happening. That can be fixed with more reordering but it's more than I'd like for the scope of this PR.