I am trying to debug the response from a post call. The post succeedes and the response is shown inside the response tab in the Chrome developer tools but the promise callback is never triggered.
Any idea why the callback is not triggered?
Here is the subscription:
this.mService.uploadFiles([file.name, new Blob()]).subscribe((response: any) => {
var myResponse = response;
//Do something with response
},
error => {
var err = `Failed with status = ${error.status}`;
});
Calling the Rest API:
public uploadFiles(files: any): Observable<any> {
let formData = new FormData();
formData.append('file', files[1], files[0]);
return this.http.post(`${this.myServiceEndpoint + "api/uploadFiles"}`, formData, {
observe: 'response'
}).pipe(map(response => response))
}
I am actually getting the following error even though the POST succeeds. Probable related to how the service is subscribing.
core.js:4002 ERROR TypeError: You provided 'undefined' where a stream was expected. You can provide an Observable, Promise, Array, or Iterable.
at subscribeTo (subscribeTo.js:28)
at subscribeToResult (subscribeToResult.js:15)
at CatchSubscriber.error (catchError.js:43)
at XMLHttpRequest.onLoad (http.js:1707)
at ZoneDelegate.push../node_modules/zone.js/dist/zone.js.ZoneDelegate.invokeTask (zone.js:423)
at Object.onInvokeTask (core.js:26247)
at ZoneDelegate.push../node_modules/zone.js/dist/zone.js.ZoneDelegate.invokeTask (zone.js:422)
at Zone.push../node_modules/zone.js/dist/zone.js.Zone.runTask (zone.js:195)
at ZoneTask.push../node_modules/zone.js/dist/zone.js.ZoneTask.invokeTask [as invoke] (zone.js:498)
at invokeTask (zone.js:1693)
[UPDATE]
Below is the server side code for the called Azure Function
[FunctionName("UploadFiles")]
public async Task<IActionResult> UploadFiles([HttpTrigger(AuthorizationLevel.Function, "post")]HttpRequest req, ILogger logger)
{
return (ActionResult)new OkObjectResult("Testing");
}
In this case Chrome displays the Testing string in the response tab. The callback is however not triggered.
... but if change the code to only return a OkResult the callback is triggered. So it seems the error appears only if the OkResult contains a payload. Any clue why this is failing?
return (ActionResult)new OkResult();
Give this a try do return response
public uploadFiles(files: any): Observable<any> {
let formData = new FormData();
formData.append('file', files[1], files[0]);
return this.http.post(`${this.myServiceEndpoint + "api/uploadFiles"}`, formData, {headers : new HttpHeaders({'Content-Type': 'application/x-www-form-urlencoded'})}).pipe(
map((response: Response) => {
return response;
})
)
}
whenever you do map in service like this do return that value so that mapped value will be return where you subscribing this service
EDIT
Try passing headers since you are passing file object in api
you can try like this
component
this.mService.uploadFiles([file.name, new Blob()]).subscribe((response: any) => {
var myResponse = response;
// here we are doing other operations like filtering and mapping etc..
myResponse.pipe(map(result => { console.log(result); }))
},
error => {
var err = `Failed with status = ${error.status}`;
});
service
public uploadFiles(files: any): Observable<any> {
let formData = new FormData();
formData.append('file', files[1], files[0]);
return this.http.post(`${this.myServiceEndpoint + "api/uploadFiles"}`, formData, {
observe: 'response'
});
}
Turns out I had to convert the response to json format on the server side (thought this was default json) and escaping the string quotes because the Angular interseptor was expecting json:
[FunctionName("UploadFiles")]
public async Task<IActionResult> UploadFiles([HttpTrigger(AuthorizationLevel.Function, "post")]HttpRequest req, ILogger logger)
{
var jsonResponse = JsonConvert.SerializeObject("Testing");
return (ActionResult)new OkObjectResult(jsonResponse);
}
and the callback is triggered successfully.
Related
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);;
}
Figured out and fixed this a while ago and figured I should come back in case it helps anyone.
Wrapping the request in a promise, and passing its resolve/reject into promises returned was the solution.
The code below isn't complete but it's enough to illustrate what had to be done to get this working as intended:
const request = function(resource, options){
return new Promise((resolve, reject) => {
return fetch(resource, options)
.then(response => {
if(
options &&
options.method == "DELETE" &&
response.status == 204
){
// --- Yii2 will return a 204 response on successful deletes and
// --- running response.json() on that will result in an error
// --- "SyntaxError: Unexpected end of JSON input" so we will just
// --- avoid that by returning an empty object
return Promise.all([response, JSON.stringify("{}"), resolve, reject])
}
// --- Include resolve/reject for proper error handling by response processing
return Promise.all([response, response.json(), resolve, reject])
}).then(this.processRestResponse)
});
}
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);
}
const processRestResponse = function([response, body, resolve, reject]){
// --- If the response is okay pass it all through to the function
// --- that will be handling a response
if(response.ok){
return resolve([response, body]);
}
// --- If there are validation errors prepare them in a string
// --- to throw a user friendly validation error message
if(
response.status == 422 &&
Array.isArray(body)
){
let messages = [];
body.forEach(validationError => {
messages.push(validationError.message);
})
return reject({
name: response.status,
message: messages.join("\n")
})
}
// --- If there is another error just provide the status text
// --- as a message (Yii provides this)
return reject({
name: response.status,
message: (body.message) ?
body.message :
response.statusText
})
}
export default {
baseUrl: '',
resourceUrl: resourceUrl,
request: request,
create: create,
processRestResponse: processRestResponse,
handleErrorResponse: handleErrorResponse
};
I was having some problem when trying to pass a parameter from jsp to controller. Here is the JavaScript where I call the API in Controller:
function help(value){
// need to pass the "value" parameter to this following API
window.open("<c:url value='doWavierGuide.do'/>", 'info', 'width=640,height=480,resizable=yes,scrollbars=yes')
}
And my controller as such:
#RequestMapping(value = "/doWavierGuide.do", method = RequestMethod.GET)
public ModelAndView showWavierGuide() {
Map<String, Object> modelMap = new HashMap<>();
log.debug("showWavierGuide() : Method is ");
// need to put the value passed in from javascript into here, temporary hardcoded
modelMap.put("method", "1");
return new ModelAndView("wavierGuide", modelMap);
}
But I not sure how can I pass the value from JavaScript to Controller. If I hardcoded the parameter value in Controller, the page managed to display. Any ideas? Thanks!
I managed to solve it by changing the url in jstl tag like this:
"<c:url value='doWavierGuide.do?method='/>"+value
And in the controller, retrieve the parameter using HttpServletRequest:
#RequestMapping(value = "/doWavierGuide.do", method = RequestMethod.GET)
public ModelAndView showWavierGuide(HttpServletRequest request) {
Map<String, Object> modelMap = new HashMap<>();
modelMap.put("method", request.getParameter("method"));
return new ModelAndView("wavierGuide", modelMap);
}
You have to use a library to make http requests to the server, or use fetch to send and receive data.
I leave an example:
const cnn = async (path, options) => {
const url = "www.example.com";
let result = await fetch(`${url}${path}`,
options
);
if (result.status >= 200 && result.status <= 299) {
return await result.json();
}
return { data: null, error: true }
}
// request to the API
cnn("doWavierGuide.do", { method: "GET", body: {name: "you_name"} })
.then(response => response.json())
.then(response => console.log(response))
I'm trying to upload a file of whatever file extension. I'm uploading with a post, but when a run the function, it crash with an error:
"Cannot ready property 'length' of undefined "
upload(fileToUpload: File): Promise<FileResponse[]> {
const formData: FormData = new FormData();
formData.append('fileKey', fileToUpload, fileToUpload.name);
return this.http
.post<FileResponse[]>(this.baseUrl + this.urlUpload, formData, { headers: {'Content-Type': undefined }})
.toPromise().then((response) => {
console.log(response);
return response;
}).catch((e) => {
console.log(e);
return e;
});
}
This is my back end c#:
[HttpPost]
[Route("upload")]
[DisableFormValueModelBinding]
[RequestSizeLimit(104857600)]
public async Task<IActionResult> Upload()
{
var fileUploaded = await _fileService.UploadFile(FormOptions, reader);
return Json(new StandardResult<List<FileResponse>>(HttpStatusCode.Created, fileUploaded).Reply);
}
any solution to this?
I think that it's for this line:
{'Content-Type': undefined }
Or delete this line or put a generic "type", for example application/octet-stream
More information: https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types
I am trying to use ES6's fetch api to post some login data to a java spark server. The GET requests work perfectly, but when I try to POST something, the Promise on the client side stays 'pending'. I checked, and the server receives the request body, and parses it to an object. Using postman, it also returns true or false, so I think something is wrong with the CORS setup. I'm new to that, so i jut let * through, thinking it should work. I am using VueJS, but i don't think that really matters here, thought I'd add this info, maybe it helps. I will post the code below.
JS:
methods: {
login: function () {
data = '"perfectly valid json string"'
this.postData('http://te.st/login', data)
},
postData: function(url, data){
return fetch(url, {
body: data,
method: 'POST',
})
.then(response => response.json())
.then(function (result){
app.response = result
})
}
}
Java:
private static void enableCORS(final String origin, final String methods, final String headers) {
options("/*", (request, response) -> {
String accessControlRequestHeaders = request.headers("Access-Control-Request-Headers");
if (accessControlRequestHeaders != null) {
response.header("Access-Control-Allow-Headers", accessControlRequestHeaders);
}
String accessControlRequestMethod = request.headers("Access-Control-Request-Method");
if (accessControlRequestMethod != null) {
response.header("Access-Control-Allow-Methods", accessControlRequestMethod);
}
return "OK";
});
before((request, response) -> {
response.header("Access-Control-Allow-Origin", origin);
response.header("Access-Control-Request-Method", methods);
response.header("Access-Control-Allow-Headers", headers);
response.type("application/json");
});
}
(called in the code as enableCORS("*","GET,POST","Origin, Content-Type, Access-Control-Allow-Origin"); )
And the endpoint:
post("/login", (req, res) -> {
boolean ret = dao.checkLogin(gson.fromJson(req.body(), User.class));
return gson.toJson(ret);
});
I use Aurelia Fetch Client library to fetch JSON data from the backend server by the code:
getData() {
let httpClient = new HttpClient();
return httpClient.fetch('http://localhost:9220/get-data')
.then(response => response.json())
.then(data => return data);
}
}
And the metod getData() is called from the another code by the code:
dataService.getData().then(data => {
this.data = data;
}).catch(error => {
this.backendError = true;
});
As you can see I use here a catch statement and in case of error it's called, but I also see in the console an error message that comes from the library: "vendor-bundle.js:1395 Unhandled rejection TypeError: Failed to fetch". How can I get rid it?
I'm unsure if this is a bug with the Aurelia HTTP Fetch Client, but adding a responseError interceptor should remove the Unhandled Exception warning in the console.
let http = new HttpClient();
http.configure(config => {
config.withInterceptor({
response(response) {
return response;
},
responseError(error) {
return error;
}
})
});
This error may also come from the UseDeveloperExceptionPage middleware in a .NET Core API. This middleware strips all headers from the response which create CORS issues and causes the "TypeError: Failed to fetch" error you saw. Here is an example of my solution, which is described in full here.
.NET Core Middleware
private static Task HandleExceptionAsync(HttpContext context, Exception exception)
{
var code = HttpStatusCode.InternalServerError;
var result = JsonConvert.SerializeObject(new { error = "An internal server error has occurred." });
context.Response.ContentType = "application/json";
context.Response.StatusCode = (int)code;
return context.Response.WriteAsync(result);
}
Aurelia Interceptor
responseError(response: any): Promise<Response> {
if (response instanceof Response) {
return response.json().then((serverError: ServerError) => {
// Do something with the error here.
return Promise.reject<Response>(serverError.error);
});
}
}