hw_19_01.html 9.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217
  1. <header>reducers</header>
  2. <body>
  3. <div id="testDiv">
  4. Test
  5. </div>
  6. <script>
  7. function jwtDecode(token) {
  8. if (!token || typeof token != "string")
  9. return undefined;
  10. let tokenArr = token.split(".");
  11. if (tokenArr.length != 3)
  12. return undefined;
  13. try {
  14. let tokenJsonStr = atob(tokenArr[1]);
  15. let tokenJson = JSON.parse(tokenJsonStr);
  16. return tokenJson;
  17. }
  18. catch {
  19. return undefined;
  20. }
  21. }
  22. function gql(url, query, vars) {
  23. let fetchSettings =
  24. {
  25. method: "POST",
  26. headers:
  27. {
  28. "Content-Type": "application/json",
  29. "Accept": "application/json"
  30. },
  31. body: JSON.stringify(
  32. {
  33. query: query,
  34. variables: vars
  35. })
  36. };
  37. return fetch(url, fetchSettings).then(res => res.json());
  38. }
  39. function signIn(login, password, url) {
  40. const loginQuery =
  41. `query login($login:String, $password:String){
  42. login(login:$login, password:$password)
  43. }`;
  44. return gql(
  45. url,
  46. loginQuery,
  47. { login: login, password: password });
  48. }
  49. function TreatAuth({ payload }, state) {
  50. let newState = { ...state };
  51. newState.auth = { ...state.auth };
  52. newState.auth.state = { token: payload.data.login, payload: jwtDecode(payload.data.login) };
  53. return newState;
  54. }
  55. function TreatBuy({ status, payload }) {
  56. }
  57. const delay = ms => new Promise(ok => setTimeout(() => ok(ms), ms))
  58. function promiseReducer(state = {}, action) {
  59. if (action) {
  60. if (action.type === 'PROMISE') {
  61. let newState = TreatPayload(action, "buy", state, TreatBuy);
  62. newState = TreatPayload(action, "auth", newState, TreatAuth);
  63. return newState;
  64. }
  65. }
  66. return state;
  67. }
  68. function TreatPayload(action, key, state, onPayloadFunc) {
  69. let result = state;
  70. let keyAction = action[key];
  71. if (keyAction) {
  72. result = changeStatus(key, state, action);
  73. if (state !== result && keyAction.status == "FULLFILLED")
  74. onPayloadFunc(keyAction, result)
  75. }
  76. return result;
  77. }
  78. function changeStatus(key, state, action) {
  79. let result = state;
  80. let actionData = action[key];
  81. if (actionData) {
  82. let stateData = state[key];
  83. let status = actionData.status;
  84. if (!stateData || stateData.status !== status) {
  85. let newStateForKey = { ...stateData, ...{ status: status } };
  86. result = { ...state };
  87. result[key] = newStateForKey;
  88. if (status == "REJECTED")
  89. newStateForKey.error = actionData.error;
  90. }
  91. }
  92. return result;
  93. }
  94. function createStore(reducer) {
  95. let state = reducer(undefined, {}) //стартовая инициализация состояния, запуск редьюсера со state === undefined
  96. let cbs = [] //массив подписчиков
  97. const getState = () => state //функция, возвращающая переменную из замыкания
  98. const subscribe = cb => (cbs.push(cb), //запоминаем подписчиков в массиве
  99. () => cbs = cbs.filter(c => c !== cb)) //возвращаем функцию unsubscribe, которая удаляет подписчика из списка
  100. function dispatch(action) {
  101. if (typeof action === 'function') { //если action - не объект, а функция
  102. return action(dispatch, getState) //запускаем эту функцию и даем ей dispatch и getState для работы
  103. }
  104. const newState = reducer(state, action) //пробуем запустить редьюсер
  105. if (newState !== state) { //проверяем, смог ли редьюсер обработать action
  106. state = newState //если смог, то обновляем state
  107. for (let cb of cbs) cb() //и запускаем подписчиков
  108. }
  109. }
  110. return {
  111. getState, //добавление функции getState в результирующий объект
  112. dispatch,
  113. subscribe //добавление subscribe в объект
  114. }
  115. }
  116. function actionPromise({ name, promise }) {
  117. return async function Exec(dispatch) {
  118. let action = { type: "PROMISE" };
  119. action[name] = { status: "PENDING" };
  120. dispatch(action) //сигнализируем redux, что промис начался
  121. try {
  122. const payload = await promise //ожидаем промиса;
  123. action = { type: "PROMISE" };
  124. action[name] = { status: "FULLFILLED", payload: payload };
  125. dispatch(action) //сигнализируем redux, что промис успешно выполнен
  126. return payload //в месте запуска store.dispatch с этим thunk можно так же получить результат промиса
  127. }
  128. catch (error) {
  129. action = { type: "PROMISE" };
  130. action[name] = { status: "REJECTED", error: error };
  131. dispatch(action) //в случае ошибки - сигнализируем redux, что промис несложился
  132. }
  133. };
  134. }
  135. const store = createStore(promiseReducer)
  136. store.subscribe(() => {
  137. let state = store.getState();
  138. if (state.buy) {
  139. if (state.buy.status == "PENDING")
  140. testDiv.style.color = "orange";
  141. else if (state.buy.status == "FULLFILLED")
  142. testDiv.style.color = "green";
  143. else if (state.buy.status == "REJECTED") {
  144. testDiv.style.color = "red";
  145. testDiv.innerText = state.buy.errorMessage;
  146. }
  147. }
  148. });
  149. let execFunc = actionPromise({ name: "auth", promise: signIn("test457", "123123", "http://shop-roles.node.ed.asmer.org.ua/graphql") });
  150. store.dispatch(execFunc);
  151. /*
  152. const actionPending = () => ({ type: 'PROMISE', status: 'PENDING' })
  153. const actionFulfilled = payload => ({ type: 'PROMISE', status: 'FULFILLED', payload })
  154. const actionRejected = error => ({ type: 'PROMISE', status: 'REJECTED', error })
  155. store.subscribe(() => console.log(store.getState()))
  156. store.dispatch({ type: 'COUNTER_INC' })
  157. store.dispatch({ type: 'BOOLEAN_SET' })
  158. store.dispatch({ type: 'COUNTER_INC' })
  159. store.dispatch({ type: 'BOOLEAN_TOGGLE' })
  160. store.dispatch({ type: 'COUNTER_DEC' })
  161. store.dispatch({ type: 'ДИЧЬ' }) //не вызывает подписчика
  162. */
  163. </script>
  164. </body>
  165. function combineReducers(reducers) {
  166. function totalReducer(totalState = {}, action) {
  167. const newTotalState = {} //объект, который будет хранить только новые состояния дочерних редьюсеров
  168. //цикл + квадратные скобочки позволяют написать код, который будет работать с любыми количеством дочерных редьюсеров
  169. for (const [reducerName, childReducer] of Object.entries(reducers)) {
  170. const newState = childReducer(totalState[reducerName], action) //запуск дочернего редьюсера
  171. if (newState !== totalState[reducerName]) { //если он отреагировал на action
  172. newTotalState[reducerName] = newState //добавляем его в newTotalState
  173. }
  174. }
  175. //Универсальная проверка на то, что хотя бы один дочерний редьюсер создал новый стейт:
  176. if (Object.values(newTotalState).length) {
  177. return { ...totalState, ...newTotalState } //создаем новый общий стейт, накладывая новый стейты дочерних редьюсеров на
  178. старые
  179. }
  180. return totalState //если экшен не был понят ни одним из дочерних редьюсеров, возвращаем общий стейт как был.
  181. }
  182. return totalReducer
  183. }
  184. //////
  185. /////