It would be useful to also know the reason of the invalid move, so allow for some extra data specific to the game.
To continue the thread from Gitter. Couple of options/considerations:
Code examples
// define custom error class
class InvalidMoveError extends Error {
constructor(data) {
super('INVALID_MOVE');
this.name = "InvalidMoveError";
this.data = data;
}
}
```js
// move definition
const { InvalidMoveError } = require('boardgame.io/core');
function move() {
// move is invalid, throw an error
throw new InvalidMoveError({ message: 'something went wrong', player: '2' });
}
```js
// process move
try {
moveFn(G, ctx, ...args);
} catch (error) {
if (!(error instanceof InvalidMoveError)) throw error;
// return error.data to client
}
Code examples
// define error creator
function InvalidMove(data) {
return { error: 'INVALID_MOVE', data }; // for example
}
```js
const { InvalidMove } = require('boardgame.io/core');
function move() {
// move is invalid, return an error object
return InvalidMove({ message: 'something went wrong', player: '2' });
}
```js
// process move
const result = moveFn(G, ctx, ...args);
if (result && result.error === 'INVALID_MOVE') {
// move is invalid, return result.data the client
}
return INVALID_MOVE approach than throwing an error.ctx could have an error methodOn the surface this seems pretty similar to an InvalidMove function to me, just that the function is attached to ctx instead of exported in core.
The major advantage of this would presumably be that it could hook into ctx in deeper ways — perhaps firing other kinds of changes to state or flow. What would those be?
How does the client receive the invalid move response? Currently when a move returns INVALID_MOVE the reducer logs an error to the console and unchanged state is returned:
Reducer code
It’s worth noting that most of the error logging calls in the reducer, could potentially also benefit from reporting to the client what went wrong.
I feel like the error should not be returned within or alongside state, because it’s really a response to the imperative API that moves provide, and the error is client-specific, not a global event, but then I’m not sure how the client should get the error object.
It’s actually a broader architectural problem: the client moves are synchronous functions you fire off and hope all goes well, but under the hood these can trigger asynchronous or error-producing code. There’s no way of knowing directly when or if they’ve completed or if they were successful; you just observe the state changes the move may cause. This API is pretty nice for most simple use cases, but it has its drawbacks. It’s also a problem with multiplayer scenarios, where we don’t know if a server call timed out etc. (e.g. #218). Are there standard patterns for reducer errors or errors in general when using a Flux approach?
One way to work around the asynchronous issue is to return a Promise from the move dispatcher. We just need to ensure that the client can still be called in a synchronous way when using an in-memory store.
Of the approaches that we've discussed so far, I think I'm leaning toward moves returning an error object.
Most helpful comment
Of the approaches that we've discussed so far, I think I'm leaning toward moves returning an error object.