I've already read tons of resources to try to help me on this. This gist did not solve it for me (https://github.com/github/fetch/issues/203#issuecomment-266034180). It also seemed like this (JavaScript Promises - reject vs. throw) would be my answer but it is not. Also this (Error thrown in awaited Promise not caught in catch block) and this (errors not being thrown after promise).
I'm developing a project using a Yii2 PHP server-side solution, and Vue frontend solution. The project has several resources (lessons, media, etc) and REST API endpoints on the server-side that all are used the same. My dev work would benefit from me creating a re-usable API client class (in native JS - not anyting Vue related). I created an 'abstract' class that I 'extend' for each resource and use its functions for the CRUD operations.
I'd like to set up some middleware functions that are going to process the response from the API so that will be handled in the same fashion after every request I make so that I don't have to reproduce that processing code in the Vue apps and components that are using those API client classes.
The code is using the native JS fetch() function. I'm using .then() and .catch() in the functions as needed to process responses and control the flow.
My problem is that I have a function to process the API response, and in it I throw an error if I receive a non-200 response. I've implemented .catch() blocks in several places but I always get an error "Uncaught (in promise)" regardless of putting catch() calls everywhere.
When a user starts watching a video, I make an API call to my server to update a status on a user_media
record. So, in the Vue component, I use my UserMedia helper class to create() a resource on the server and implement a then() and catch() on that. When there is an error server-side, I expect the catch() to catch that error and handle it. But, I just get the error "Uncaught (in promise)" as if I'm not trying to catch the error at all.
In the code, I am using updateWatchedStatus() in the vimeo video component, that calls the UserMediaApi.create() which calls YiiApiHelper.request() which calls YiiApiHelper.processRestResponse() where the error is thrown. I've tried implementing catch() blocks all over the place but it's never caught.
CLEARLY, I don't understand something about either fetch(), promises, or catching errors. But I can't figure it out. It seems like the only way around this is to have to write a bunch more code to try to compensate. Any help is appreciated. Even if I'm going about this all wrong and should be doing it someway else entirely.
The full code for that can be seen here:
YiiApiHelper.js https://pastebin.com/HJNWYQXg
UserMediaApi.js https://pastebin.com/9u8jkcSP
Vimeo Video Vue Component https://pastebin.com/4dJ1TtdM
For brevity, here's what's important:
Generic API Helper:
const request = function(resource, options){
return fetch(resource, options)
.then(response => Promise.all([response, response.json()]));
}
const resourceUrl = function(){
return this.autoPluralizeResource ?
this.resourceName+'s' :
this.resourceName;
}
const create = function(postData, options){
const url = new URL(this.baseUrl+'/'+this.resourceUrl());
if(!options){
options = {};
}
options = {
method: 'POST',
body: JSON.stringify(postData),
...options,
}
if(!options.headers){
options.headers = {};
}
options.headers = {
'X-CSRF-Token': document.querySelector('meta[name="csrf-token"]').getAttribute('content'),
"Content-Type": "application/json",
...options.headers
}
return this.request(url, options)
.then(this.processRestResponse);
}
const processRestResponse = function([response, body]){
if(!response.ok){
if(response.status == 422){
if(Array.isArray(body)){
let messages = [];
body.forEach(validationError => {
messages.push(validationError.message);
})
throw {
name: response.status,
message: messages.join("\n")
}
}
}
throw {
name: response.status,
message: (body.message) ?
body.message :
response.statusText
}
}
return Promise.all([response, body]);
}
export default {
baseUrl: '',
resourceName: '',
autoPluralizeResource: true,
resourceUrl: resourceUrl,
request: request,
create: create,
processRestResponse: processRestResponse,
handleErrorResponse: handleErrorResponse
};
UserMedia helper:
import YiiApiHelper from './../../yiivue/YiiApiHelper.js';
export default {
...YiiApiHelper,
baseUrl: window.location.origin+'/media/api/v1',
resourceName: 'user-media',
autoPluralizeResource: false
}
VimeoVideo.js:
let updateWatchedStatus = function(watchedStatusId) {
if(!props.userMedia){
// --- User has no record for this media, create one
return UserMediaApi.create({
media_id: props.media.id,
user_id: props.userId,
data: {
[Helper.WATCHED_STATUS_KEY]: watchedStatusId
}
}).then(([response, body]) => {
context.emit('userMediaUpdated', {userMedia: body});
return body;
}).catch(YiiApiHelper.handleErrorResponse);;
}
// --- User has a record, update the watched status in the data
let data = {
...userMedia.value.data,
[Helper.WATCHED_STATUS_KEY]: watchedStatusId
}
return UserMediaApi.update(props.media.id+','+props.userId, {
data: data
}).then(([response, body]) => {
context.emit('userMediaUpdated', {userMedia: body});
return body;
}).catch(YiiApiHelper.handleErrorResponse);;
}