Gql_promis.html 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218
  1. <Header>Gql</Header>
  2. <body>
  3. <script>
  4. function jwtDecode(token) { // расщифровки токена авторизации
  5. if (!token || typeof token != "string")
  6. return undefined;
  7. let tokenArr = token.split(".");
  8. if (tokenArr.length != 3)
  9. return undefined;
  10. try {
  11. let tokenJsonStr = atob(tokenArr[1]);
  12. let tokenJson = JSON.parse(tokenJsonStr);
  13. return tokenJson;
  14. }
  15. catch {
  16. return undefined;
  17. }
  18. }
  19. function combineReducers(reducers) {
  20. function totalReducer(totalState = {}, action) {
  21. const newTotalState = {} //объект, который будет хранить только новые состояния дочерних редьюсеров
  22. //цикл + квадратные скобочки позволяют написать код, который будет работать с любыми количеством дочерных редьюсеров
  23. for (const [reducerName, childReducer] of Object.entries(reducers)) {
  24. const newState = childReducer(totalState[reducerName], action) //запуск дочернего редьюсера
  25. if (newState !== totalState[reducerName]) { //если он отреагировал на action
  26. newTotalState[reducerName] = newState //добавляем его в newTotalState
  27. }
  28. }
  29. //Универсальная проверка на то, что хотя бы один дочерний редьюсер создал новый стейт:
  30. if (Object.values(newTotalState).length) {
  31. return { ...totalState, ...newTotalState } //создаем новый общий стейт, накладывая новый стейты дочерних редьюсеров на старые
  32. }
  33. return totalState //если экшен не был понят ни одним из дочерних редьюсеров, возвращаем общий стейт как был.
  34. }
  35. return totalReducer
  36. }
  37. function promiseReducer(state = {}, action) { // диспетчер обработки
  38. if (action) {
  39. if (action.type === 'PROMISE') {
  40. let newState = { ...state };
  41. newState[action.name] = { status: action.status, payload: action.payload, error: action.error };
  42. return newState;
  43. }
  44. }
  45. return state;
  46. }
  47. function authReducer(state = {}, action) { // диспетчер обработки login
  48. if (action) {
  49. if (action.type === 'AUTH_LOGIN') {
  50. let newState = { ...state };
  51. newState.token = action.token;
  52. newState.payload = jwtDecode(action.token);
  53. if (!newState.payload) {
  54. newState.token = undefined;
  55. }
  56. return newState;
  57. }
  58. else if (action.type === 'AUTH_LOGOUT') {
  59. let newState = { ...state };
  60. newState.token = undefined;
  61. newState.payload = undefined;
  62. return newState;
  63. }
  64. }
  65. return state;
  66. }
  67. function createStore(reducer) {
  68. let state = reducer(undefined, {}) //стартовая инициализация состояния, запуск редьюсера со state === undefined
  69. let cbs = [] //массив подписчиков
  70. const getState = () => state //функция, возвращающая переменную из замыкания
  71. const subscribe = cb => (cbs.push(cb), //запоминаем подписчиков в массиве
  72. () => cbs = cbs.filter(c => c !== cb)) //возвращаем функцию unsubscribe, которая удаляет подписчика из списка
  73. function dispatch(action) {
  74. if (typeof action === 'function') { //если action - не объект, а функция
  75. return action(dispatch, getState) //запускаем эту функцию и даем ей dispatch и getState для работы
  76. }
  77. const newState = reducer(state, action) //пробуем запустить редьюсер
  78. if (newState !== state) { //проверяем, смог ли редьюсер обработать action
  79. state = newState //если смог, то обновляем state
  80. for (let cb of cbs) cb() //и запускаем подписчиков
  81. }
  82. }
  83. return {
  84. getState, //добавление функции getState в результирующий объект
  85. dispatch,
  86. subscribe //добавление subscribe в объект
  87. }
  88. }
  89. function gql(url, query, vars) {
  90. let fetchSettings =
  91. {
  92. method: "POST",
  93. headers:
  94. {
  95. "Content-Type": "application/json",
  96. "Accept": "application/json"
  97. },
  98. body: JSON.stringify(
  99. {
  100. query: query,
  101. variables: vars
  102. })
  103. };
  104. return fetch(url, fetchSettings).then(res => res.json());
  105. }
  106. function actionPromiseGql(name, promise) {
  107. return async function Exec(dispatch) {
  108. dispatch(actionPending(name)) //сигнализируем redux, что промис начался
  109. try {
  110. const payload = await promise; //ожидаем промиса;
  111. let result = Object.values(payload.data)[0];
  112. dispatch(actionFulfilled(name, result)); //сигнализируем redux, что промис успешно выполнен
  113. return result; //в месте запуска store.dispatch с этим thunk можно так же получить результат промиса
  114. }
  115. catch (error) {
  116. dispatch(actionRejected(name, error)) //в случае ошибки - сигнализируем redux, что промис несложился
  117. }
  118. };
  119. }
  120. function actionAuthGql(promise) {
  121. return async function Exec(dispatch) {
  122. try {
  123. const payload = await promise //ожидаем промиса;
  124. let result = Object.values(payload.data)[0];
  125. dispatch(actionAuthLogin(result)); //сигнализируем redux, что промис успешно выполнен
  126. return result; //в месте запуска store.dispatch с этим thunk можно так же получить результат промиса
  127. }
  128. catch (error) {
  129. dispatch(actionLogOut()) //в случае ошибки - сигнализируем redux, что промис несложился
  130. }
  131. };
  132. }
  133. const actionPending = (name) => ({ type: 'PROMISE', name: name, status: 'PENDING' });
  134. const actionFulfilled = (name, payload) => ({ type: 'PROMISE', name: name, payload: payload, status: 'FULFILLED' });
  135. const actionRejected = (name, error) => ({ type: 'PROMISE', name: name, error: error, status: 'REJECTED' });
  136. const actionAuthLogin = token => ({ type: 'AUTH_LOGIN', token });
  137. const actionAuthLogout = () => ({ type: 'AUTH_LOGOUT' });
  138. const gqlRootCats = () => {
  139. const catQuery = `query roots {
  140. CategoryFind(query: "[{\\"parent\\": null }]") {
  141. _id name
  142. }}`;
  143. return gql("http://shop-roles.node.ed.asmer.org.ua/graphql", catQuery);
  144. }
  145. const actionRootCats = () =>
  146. actionPromiseGql('rootCats', gqlRootCats());
  147. ///////////////////////////////////////
  148. const gqlCategoryFindOne = (id) => {
  149. const catQuery = `query CategoryFindOne($q: String) {
  150. CategoryFindOne(query: $q) {
  151. _id name
  152. parent { _id name }
  153. subCategories { _id name }
  154. goods { _id name price description
  155. images { url }
  156. }
  157. }
  158. }`;
  159. return gql("http://shop-roles.node.ed.asmer.org.ua/graphql", catQuery, { q: `[{\"_id\": \"${id}\"}]` });
  160. }
  161. const actionCategoryFindOne = (id) =>
  162. actionPromiseGql('catFindOne', gqlCategoryFindOne(id));
  163. /////////////////////////////////////////
  164. const gqlGoodFindOne = (id) => {
  165. const catQuery = `
  166. query GoodFindOne($q: String) {
  167. GoodFindOne(query: $q) {
  168. _id name price description
  169. images {
  170. url
  171. }
  172. }
  173. }
  174. `;
  175. return gql("http://shop-roles.node.ed.asmer.org.ua/graphql", catQuery, { q: `[{\"_id\": \"${id}\"}]` });
  176. }
  177. const actionGoodFindOne = (id) =>
  178. actionPromiseGql('goodsFindOne', gqlGoodFindOne(id));
  179. //////////////////////////////////
  180. const gqlAuthLogin = (login, password) => {
  181. const loginQuery = `query login($login:String, $password:String){
  182. login(login:$login, password:$password)
  183. }`;
  184. return gql("http://shop-roles.node.ed.asmer.org.ua/graphql", loginQuery, { login: login, password: password });
  185. }
  186. const actionAuthLoginFunc = (login, password) =>
  187. actionAuthGql(gqlAuthLogin(login, password));
  188. ////////////////////////////////////////
  189. const store = createStore(combineReducers({ promiseReducer, authReducer }));
  190. store.subscribe(() => {
  191. console.log(store.getState())
  192. });
  193. //store.dispatch(actionRootCats());
  194. //store.dispatch(actionCategoryFindOne("6262ca7dbf8b206433f5b3d1"));
  195. //store.dispatch(actionGoodFindOne("62d3099ab74e1f5f2ec1a125"));
  196. store.dispatch(actionAuthLoginFunc("Berg", "123456789"));
  197. </script>
  198. </body>