Reputation: 1449
I'm having an issue with my fetch returning any data from the server and I can't quite seem to understand why.
I thought maybe the promise was resolving before the server was sending back any JSON so I recently updated my model to utilize promises as well. I'm new to promises and the fetch API so bear with me.
I am willing to revert back too callbacks and put my
Player.find
andPlayer.create
methods back in thePlayers.js
route and avoid promises on the server side altogether.
Player Model (after adding promise)
// require mongoose for data modeling
import mongoose from 'mongoose';
import Q from 'q';
const Schema = mongoose.Schema;
// define player schema
const PLAYER_SCHEMA = new Schema({
name: String,
score: Number
});
PLAYER_SCHEMA.statics.createPlayer = (name, score) => {
const deferred = Q.defer();
Player.create({
name,
score,
}, (error, player) => {
if (error) {
deferred.reject(new Error(error));
}
deferred.resolve(player);
});
return deferred.promise;
}
PLAYER_SCHEMA.statics.getPlayers = () => {
const deferred = Q.defer();
Player.find({})
.sort({
score: 'desc'
})
.limit(5)
.exec((error, players) => {
if (error) {
deferred.reject(new Error(error));
}
deferred.resolve(players);
});
return deferred.promise;
}
let Player = mongoose.model('Player', PLAYER_SCHEMA);
// first param is the singular name for the collection,
// mongoose automatically looks for the plural version
// so our db will have a 'players' collection
export default Player;
Route (after adding promise)
// dependencies
import express from 'express';
import Player from '../models/player';
const ROUTER = express.Router();
ROUTER.post('/', (req, res) => {
const name = req.body.name;
const score = req.body.score;
const promise = Player.createPlayer(name, score);
promise.then((response) => {
res.status(201).json(response);
}).catch((err) => {
res.send(err)
});
});
ROUTER.get('/', (req, res) => {
const promise = Player.getPlayers();
promise.then((response) => {
console.log(response);
res.status(200).json(response);
}).catch((err) => {
res.send(err)
});
});
export default ROUTER;
Testing these endpoints works fine in Postman, so I assume the issue does not lie on the server.
This is where I suspect I have an issue.
Client.js (where AJAX methods are stored)
function createPlayer(playerData, onError, cb) {
return fetch('/api/players', {
method: 'post',
body: JSON.stringify(playerData),
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
},
}).then(checkStatus)
.then(cb)
.catch(onError);
}
function getPlayers(success, onError) {
return fetch('/api/players', {
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
},
}).then(checkStatus)
.then(parseJSON)
.then(success)
.catch(onError);
}
function checkStatus(response) {
if (response.status >= 200 && response.status < 300) {
return response;
} else {
const error = new Error(`HTTP Error ${response.statusText}`);
error.status = response.statusText;
error.response = response;
throw error;
}
}
function parseJSON(response) {
return response.json();
}
const Client = {
createPlayer,
getPlayers
};
export default Client;
App.jsx (a lot of code omitted)
// all other code omitted
const playerData = {
name,
score: this.state.totalScore,
};
client.createPlayer(playerData,
(err) => {
// TODO: improve error message
console.log(err);
},
client.getPlayers((data) => {
// array of objects containing all but most recent entry
console.log(data);
this.setState({
inputScreen: false, // hide the input screen
highScoreScreen: true, // show the high score screen
yeti: yetiWin,
highScores: [...this.state.highScores, ...data],
});
}, (err) => {
// TODO: improve this error message
console.log(err);
})
);
// all other code omitted
The data variable never contains the newly created player, but it does contain other players. So the create and read operations are working on the server-side. When I modify my App.jsx
to something like below:
App.jsx (trial)
const playerData = {
name,
score: this.state.totalScore,
};
client.createPlayer(playerData,
(err) => {
// TODO: improve error message
console.log(err);
},
(res) => {
// should contain something like:
// {
// "__v": 0,
// "_id": "5881922f441fda40ccc52f83",
// "name": "player name",
// "score": "123"
// }
// right?
console.log(res.json());
console.log(res);
console.log(res.body);
}
);
When I log res.json
res
and res.body
I get:
Promise {[[PromiseStatus]]: "pending", [[PromiseValue]]: undefined}
Response {type: "basic", url: "http://localhost:3000/api/players", status: 200, ok: true, statusText: "OK"…}
ReadableStream {}
When I test my POST endpoint (http://localhost:3001/api/players
) in postman the server responds with a JSON object containing the data I want. For whatever reason client.createPlayer
doesn't return the data from the server. Of course, that's only part of the problem.
In theory, once my client.createPlayer
resolves and I invoke client.getPlayers
as the callback to client.createPlayer
that method should return the data I want including the newly created player, right?
However that doesn't seem to be the case.
What am I doing wrong?
Upvotes: 1
Views: 1884
Reputation: 1449
Interestingly enough, by changing the code in my App.jsx
to the following I was able to produce the results I'm after. However, I'm not going to mark my answer as the correct solution as I do not fully understand why this change made it work.
If anyone would like to chime in and explain why this now works I would love to hear the reason why.
App.jsx (fixed)
// all other code omitted
client.createPlayer(playerData, (err) => {
console.error(err);
}, (data) => {
this.setState({
highScores: [...this.state.highScores, ...data],
});
});
client.getPlayers((data) => {
this.setState({
inputScreen: false, // hide the input screen
highScoreScreen: true, // show the high score screen
yeti: yetiWin,
highScores: [...this.state.highScores, ...data],
});
}, (err) => {
console.error(err);
});
// all other code omitted
You'll note that I am no longer invoking client.getPlayers
as the success callback to client.createPlayer
. On top of that I'm using the returned data from client.createPlayer
and adding it to the state as a fallback in the event the client.createPlayer
method does not finish before the client.getPlayers
method. This would ensure that the new player is displayed once setState
issues a re-render.
Upvotes: 1