After receiving the result value of the refresh function, axiosInstace is executed before saving the accesstoken to AsyncStorage, so the updated token cannot be retrieved in axios.js through AsyncStorage.getItem. i want to save accesstoken first in refresh and get acecesstoken in axios.js and send to axiosInstace
How can I solve this problem?
this is my code
(saga.js)
function getPostAPI(data) {
return axiosInstace.post('/kakao/getpost', data);
}
function* getPost(action) {
try {
const result = yield call(getPostAPI, action.data);
yield put({
type: GETPOST_SUCCESS,
data: result.data,
});
} catch (err) {
if (err.response.data === 'jwtEx') {
yield put({
type: REFRESH_REQUEST,
// data: action.data,
});
yield put({
type: GETPOST_REQUEST,
data: action.data,
});
} else {
yield put({
type: GETPOST_FAILURE,
error: err.response.data,
});
}
}
}
function refreshAPI() {
return axiosInstace.post('/kakao/refresh');
}
function* refresh() {
try {
const result = yield call(refreshAPI);
yield AsyncStorage.setItem(
'accesstoken',
`${result.data.accessToken}`,
() => {
// console.log('accesstoken 재발급 저장 완료');
},
);
yield put({
type: REFRESH_SUCCESS,
data: result.data,
});
} catch (err) {
yield put({
type: REFRESH_FAILURE,
error: err.response.data,
});
}
}
(axios.js)
AxiosInstance.interceptors.request.use(async (cfg) => {
const acecesstoken = await AsyncStorage.getItem('accesstoken');
const refreshtoken = await AsyncStorage.getItem('refreshtoken');
if (acecesstoken) {
cfg.headers.Authorization = `Bearer ${acecesstoken} ${refreshtoken}`;
}
return cfg;
});
export default AxiosInstance;
A simple solution would be to call your refresh() generator directly:
function* getPost(action) {
try {
const result = yield call(getPostAPI, action.data);
yield put({
type: GETPOST_SUCCESS,
data: result.data,
});
} catch (err) {
if (err.response.data === 'jwtEx') {
yield call(refresh);
// you could also redispatch the original action
yield put(action);
} else {
yield put({
type: GETPOST_FAILURE,
error: err.response.data,
});
}
}
}
Alternatively your can start a race between REFRESH_SUCCESS and REFRESH_FAILURE:
const { success, failure } = yield race({
success: take('REFRESH_SUCCESS'),
failure: take('REFRESH_FAILURE'),
});
if(success) {
// continue
} else {
// handle refresh failure
}
Related
If an error occurs in the yield call(refresh) of the getPost function, the GETPOST_REQUEST action continues regardless of whether an error has occurred.
However, if an error occurs in the getPost function I don’t want the action to run anymore and stop and end like
yield put({
type: REFRESH_FAILURE,
error: err.response.data,
});
this is my code how can i fix?
function getPostAPI(data) {
return axiosInstace.post("/kakao/getpost", data);
}
function* getPost(action) {
try {
const result = yield call(getPostAPI, action.data);
yield put({
type: GETPOST_SUCCESS,
data: result.data,
});
} catch (err) {
if (err.response.data === "jwtEx") {
yield call(refresh); // if this error i want to stop .
yield put(action);
} else {
yield put({
type: GETPOST_FAILURE,
error: err.response.data,
});
}
}
}
function refreshAPI() {
// console.log('data::', data);
return axiosInstace.post("/kakao/refresh");
}
function* refresh() {
try {
const result = yield call(refreshAPI);
yield AsyncStorage.setItem(
"accesstoken",
`${result.data.accessToken}`,
() => {
// console.log('accesstoken 재발급 저장 완료');
console.log("accesstoken3333333333333333333", result.data.accessToken);
}
);
yield put({
type: REFRESH_SUCCESS,
data: result.data,
});
} catch (err) {
console.log("refresh err.response.data:", err.response.data);
yield put({
type: REFRESH_FAILURE,
error: err.response.data,
});
}
}
You can make a small update to the code by doing the following:
return a boolean value from your refresh generator function.
The boolean would be:
true if refresh was successful
false if refresh failed.
set yield call(refresh); to a const declaration to capture this returned boolean value
if this captured boolean value is false, exit the generator function by returning early
Try the code below.
function getPostAPI(data) {
return axiosInstace.post('/kakao/getpost', data);
}
function* getPost(action) {
try {
const result = yield call(getPostAPI, action.data);
yield put({
type: GETPOST_SUCCESS,
data: result.data,
});
} catch (err) {
if (err.response.data === 'jwtEx') {
const refreshSuccess = yield call(refresh);
if (!refreshSuccess) {
return;
}
yield put(action);
} else {
yield put({
type: GETPOST_FAILURE,
error: err.response.data,
});
}
}
}
function refreshAPI() {
return axiosInstace.post('/kakao/refresh');
}
/**
* #returns {boolean}
* - `true` if refresh is successful
* - `false` if refresh failed
*/
function* refresh() {
let refreshSuccess;
try {
const result = yield call(refreshAPI);
yield AsyncStorage.setItem(
'accesstoken',
`${result.data.accessToken}`,
() => {
// console.log('accesstoken 재발급 저장 완료');
console.log('accesstoken3333333333333333333', result.data.accessToken);
}
);
yield put({
type: REFRESH_SUCCESS,
data: result.data,
});
refreshSuccess = true;
} catch (err) {
console.log('refresh err.response.data:', err.response.data);
yield put({
type: REFRESH_FAILURE,
error: err.response.data,
});
refreshSuccess = false;
} finally {
return refreshSuccess;
}
}
I am using the saga library. And tokens are stored in AsyncStorage.
What I want is to freely use the token obtained from AsyncStorage in the loadUserPosts function or in loadPosts.
In this case, where should async be added and how do I fix the code?
this is my code
const token = await AsyncStorage.getItem('tokenstore');
function* loadUserPosts(action) {
try {
console.log(token)
yield put({
type: LOAD_USER_POSTS_SUCCESS,
data: result.data,
});
} catch (err) {
}
}
function* loadPosts(action) {
try {
console.log(token)
yield put({
type: LOAD_POSTS_SUCCESS,
data: result.data,
});
} catch (err) {
}
function* watchLoadPost() {
yield takeLatest(LOAD_POST_REQUEST, loadPosts);
}
function* watchLoadUserPosts() {
yield throttle(5000, LOAD_USER_POSTS_REQUEST, loadUserPosts);
}
export default function* postSaga() {
yield all([
fork(watchLoadPosts),
fork(watchLoadUserPosts),
]);
}
You can try and yield you async result. You might not even need async becuase generator function will yield until it gets a result.
function* loadUserPosts(action) {
try {
const token = yield AsyncStorage.getItem('tokenstore');
console.log(token)
yield put(LOAD_USER_POSTS_SUCCESS(token));
} catch (err) {
}
In a React app, I do not understand why the Yield line in a generator function is not 'waiting'? Specifically, in the LOGIN function below, I would expect the Yield line immediately following console.log("Step 3") to pause until it was completed; however it does NOT pause and Step 8 is immediately processed. I would expect the STEPS in the console.log to follow the logical numerical order. The actual order that is printed out in the browser console window is: 1,2,3,8,9,10,4,5,6,7. Can someone explain why it is NOT pausing?
export function* LOGIN({ payload }) {
const { email, password } = payload
yield put({
type: 'user/SET_STATE',
payload: {
loading: true,
},
})
let userCog
try {
console.log("Step 1")
userCog = yield call(login, email, password)
console.log("Step 2")
} catch (err) {
if (err.code === 'UserNotConfirmedException') {
yield put({
type: 'user/SET_STATE',
payload: {
loading: true,
email,
},
})
yield history.push('/system/verification')
}
}
console.log("Step 3")
yield put({
type: 'user/LOAD_CURRENT_ACCOUNT',
})
console.log("Step 8")
if (userCog) {
console.log("Step 9")
yield history.push('/dashboard/analytics')
console.log("Step 10")
}
}
export function* LOAD_CURRENT_ACCOUNT() {
yield put({
type: 'user/SET_STATE',
payload: {
loading: true,
},
})
console.log("Step 4")
const response = yield call(currentUser)
console.log("Step 5")
if (response) {
const { username } = response
yield put({
type: 'user/SET_STATE',
payload: {
id: '123',
name: 'Administrator',
email: username,
role: 'admin',
authorized: true,
},
})
}
console.log("Step 6")
yield put({
type: 'user/SET_STATE',
payload: {
loading: false,
},
})
console.log("Step 7")
}
EDIT: Here is the redux dispatch from the Login UI Component
onSubmit = event => {
event.preventDefault()
const { form, dispatch } = this.props
form.validateFields((error, values) => {
if (!error) {
dispatch({
type: 'user/LOGIN',
payload: values,
})
}
})
}
I'm trying to make two request, one to save an image an other to save a product with the url from obtained from the first requests
This is what I want to do
first: save product image (I'm using axios for requests)
second: get the url from 'productImage' and then include it in the params to save
this is my code
function* createProduct(action) {
const { endpoint, params } = action.payload;
try {
const productImage = yield call(new api().createImage, { endpoint, params });
// I need to wait the url of the image and include it on the params for the second request before is executed
// E.g. params.image = productImage.url
const product = yield call(new api().createProduct, { endpoint, params });
yield put({
type: CREATE_PRODUCT_SUCCEED,
payload: {
product
}
});
} catch (e) {
yield put({
type: CREATE_PRODUCT_FAILED,
payload: {
...e
}
});
}
}
export default function* createProductWatcher() {
yield takeEvery(CREATE_PRODUCT_EXECUTION, createProduct);
}
The best pattern here is to split your saga (createProduct) into two separate sagas:
createImage - will handle the image creation for the product
createProduct - will handle the product creation with the given image
// Creates the product image
function* createImage(action) {
const { endpoint, params } = action.payload;
try {
const image = yield call(new api().createImage, { endpoint, params });
yield put({
type: CREATE_IMAGE_SUCCEED,
// Here you pass the modified payload to createProduct saga
payload: {
endpoint,
params: { image: image.url }
}
});
} catch(e) {
yield put({
type: CREATE_IMAGE_FAILED,
payload: {
...e
}
});
}
}
//Creates the product with the image
function* createProduct(action) {
const { endpoint, params } = action.payload;
try {
const product = yield call(new api().createImage, { endpoint, params });
yield put({
type: CREATE_PRODUCT_SUCCEED,
payload: {
product
}
});
} catch(e) {
yield put({
type: CREATE_PRODUCT_FAILED,
payload: {
...e
}
});
}
}
Then use the builtin yield* operator to compose multiple Sagas in a sequential way.
// Another saga for chaining the results
function* invokeSagasInOrder(sagas) {
try {
const image = yield* createImage();
yield* createProduct(image);
} catch(e) {
console.log(e);
}
}
Welcome to stackoverflow!
export function* onFetchTree() {
yield takeLatest('FETCH_TREE', function* () {
try {
const response = yield call(fetch, '/myApi/user', {
method: 'GET',
headers: {
accept: 'application/json'
}
})
const responseBody = response.json();
yield put({ type: 'SET_TREE', payload: responseBody });
} catch (e) {
// yield put(fetchFailed(e));
return;
}
});
}
Learning to work with sagas, stuck on getting the actual data into my redux store. The above code which sends responseBody to the payload gives me a Promise object (because .json() returns that) which is great, except that I can't access the resolved Promise. I ended up on What does [[PromiseValue]] mean in javascript console and how to do I get it but this doesn't seem to work for me. I've tried adding .then() in a few ways, no luck. It seems to prevent the generator function from running at all.
If I just use response I get a Response object, which doesn't have the payload. What am I missing here? How do I get the right payload?
You need to wait for the server to send back the response.
export async function* onFetchTree() {
yield takeLatest('FETCH_TREE', function* () {
try {
const response = yield call(fetch, '/myApi/user', {
method: 'GET',
headers: {
accept: 'application/json'
}
})
const responseBody = await response.json()
yield put({ type: 'SET_TREE', payload: responseBody )}
};
} catch (e) {
// yield put(fetchFailed(e));
return;
}
});
}
I followed a pattern I found on this page that ended up working for me. I don't fully understand why the fetchTree helper is needed, but it doesn't work without it.
https://www.sigient.com/blog/managing-side-effects-with-redux-saga-a-primer-1
function fetchJson(url) {
return fetch(url, {
method: 'GET',
headers: {
accept: 'application/json'
}
})
.then(response => {
if (!response.ok) {
const error = new Error(response.statusText);
error.response = response;
throw error;
}
return response.json();
});
}
function fetchTree() {
return fetchJson('/myApi/user');
}
export function* onFetchTree() {
try {
const tree = yield call(fetchTree);
yield put({ type: 'SET_TREE', payload: tree });
} catch (e) {
yield put({
type: 'ERROR',
payload: e,
error: true,
});
}
}