Dans redux-saga, l'équivalent de l'exemple ci-dessus serait
export function* loginSaga() {
while(true) {
const { user, pass } = yield take(LOGIN_REQUEST)
try {
let { data } = yield call(request.post, '/login', { user, pass });
yield fork(loadUserData, data.uid);
yield put({ type: LOGIN_SUCCESS, data });
} catch(error) {
yield put({ type: LOGIN_ERROR, error });
}
}
}
export function* loadUserData(uid) {
try {
yield put({ type: USERDATA_REQUEST });
let { data } = yield call(request.get, `/users/${uid}`);
yield put({ type: USERDATA_SUCCESS, data });
} catch(error) {
yield put({ type: USERDATA_ERROR, error });
}
}
La première chose à remarquer est que nous allons appeler les fonctions de l'api à l'aide du formulaire yield call(func, ...args)
. call
n'exécute pas l'effet, il crée un objet ordinaire comme {type: 'CALL', func, args}
. L'exécution est déléguée à la redux-saga middleware qui prend soin de l'exécution de la fonction et de reprendre le générateur de son résultat.
Le principal avantage est que vous pouvez tester le générateur à l'extérieur de Redux à l'aide de simples contrôles d'égalité
const iterator = loginSaga()
assert.deepEqual(iterator.next().value, take(LOGIN_REQUEST))
// resume the generator with some dummy action
const mockAction = {user: '...', pass: '...'}
assert.deepEqual(
iterator.next(mockAction).value,
call(request.post, '/login', mockAction)
)
// simulate an error result
const mockError = 'invalid user/password'
assert.deepEqual(
iterator.throw(mockError).value,
put({ type: LOGIN_ERROR, error: mockError })
)
Notez que nous sommes se moquant de l'api résultat de l'appel par simple injection de la moqué de données dans l' next
méthode de l'itérateur. Se moquant de données est plus simple que de se moquant de fonctions.
La deuxième chose à remarquer, c'est l'appel à l' yield take(ACTION)
. Les Thunks sont appelés par l'action du créateur sur chaque nouvelle action (par exemple, LOGIN_REQUEST
). c'est à dire les actions sont constamment poussés à thunks, et les thunks n'avons aucun contrôle sur le moment de cesser le traitement de ces actions.
Dans redux-saga, générateurs de tirer de la prochaine action. c'est à dire qu'ils ont le contrôle lors de l'écouter un peu d'action, et quand ne pas. Dans l'exemple ci-dessus, le flux d'instructions sont placées à l'intérieur d'un while(true)
boucle, donc ça va écouter pour chaque nouvelle action, qui imite un peu le thunk poussant comportement.
L'approche "pull" permet de mettre en œuvre complexe des flux de contrôle. Supposons, par exemple, nous voulons ajouter les exigences suivantes
Poignée de DÉCONNEXION action de l'utilisateur
lors de la première connexion réussie, le serveur renvoie un jeton qui expire dans un peu de retard stockées dans un expires_in
champ. Nous allons actualiser l'autorisation en arrière-plan sur chaque expires_in
millisecondes
Prendre en compte que lors de l'attente pour le résultat des appels d'api (soit votre première connexion ou actualiser) l'utilisateur peut déconnexion entre les deux.
Comment voulez-vous mettre en œuvre qu'avec les thunks; tout en fournissant de l'essai complet de la couverture de l'ensemble des flux? Voici comment il peut regarder avec les Sagas:
function* authorize(credentials) {
const token = yield call(api.authorize, credentials)
yield put( login.success(token) )
return token
}
function* authAndRefreshTokenOnExpiry(name, password) {
let token = yield call(authorize, {name, password})
while(true) {
yield call(delay, token.expires_in)
token = yield call(authorize, {token})
}
}
function* watchAuth() {
while(true) {
try {
const {name, password} = yield take(LOGIN_REQUEST)
yield race([
take(LOGOUT),
call(authAndRefreshTokenOnExpiry, name, password)
])
// user logged out, next while iteration will wait for the
// next LOGIN_REQUEST action
} catch(error) {
yield put( login.error(error) )
}
}
}
Dans l'exemple ci-dessus, nous sommes en exprimant notre exigence de simultanéité à l'aide de race
. Si take(LOGOUT)
gagne la course (c'est à dire l'utilisateur a cliqué sur un Bouton de Déconnexion). La course entraînera automatiquement l'annulation de l' authAndRefreshTokenOnExpiry
tâche de fond. Et si l' authAndRefreshTokenOnExpiry
a été bloqué au milieu d'un call(authorize, {token})
appel il va également être annulée. L'annulation se propage à la baisse automatiquement.
Vous pouvez trouver un praticable de démonstration de la au-dessus de flux