hw_19_main traff copy.html 10 KB

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