Gql_promis_.html 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245
  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. const actionPromiseGql = (name, promise) => {
  107. return actionPromiseGqlInt = async (dispatch) => {
  108. dispatch(actionPending(name)) //сигнализируем redux, что промис начался
  109. try {
  110. const payload = await promise //ожидаем промиса
  111. dispatch(actionFulfilled(name, payload)) //сигнализируем redux, что промис успешно выполнен
  112. return payload //в месте запуска store.dispatch с этим thunk можно так же получить результат промиса
  113. }
  114. catch (error) {
  115. dispatch(actionRejected(name, error)) //в случае ошибки - сигнализируем redux, что промис несложился
  116. }
  117. }
  118. }
  119. /*function actionAuthGql(promise) {
  120. return async function Exec(dispatch) {
  121. try {
  122. const payload = await promise //ожидаем промиса;
  123. let result = Object.values(payload.data)[0];
  124. dispatch(actionAuthLogin(result)); //сигнализируем redux, что промис успешно выполнен
  125. return result; //в месте запуска store.dispatch с этим thunk можно так же получить результат промиса
  126. }
  127. catch (error) {
  128. dispatch(actionLogOut()) //в случае ошибки - сигнализируем redux, что промис несложился
  129. }
  130. };
  131. }*/
  132. const actionPending = (name) => ({ type: 'PROMISE', name: name, status: 'PENDING' });
  133. const actionFulfilled = (name, payload) => ({ type: 'PROMISE', name: name, payload: payload, status: 'FULFILLED' });
  134. const actionRejected = (name, error) => ({ type: 'PROMISE', name: name, error: error, status: 'REJECTED' });
  135. const actionAuthLogin = token => ({ type: 'AUTH_LOGIN', token });
  136. const actionAuthLogout = () => ({ type: 'AUTH_LOGOUT' });
  137. ///////////////////////////////////////
  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. const gqlCategoryFindOne = (id) => {
  148. const catQuery = `query CategoryFindOne($q: String) {
  149. CategoryFindOne(query: $q) {
  150. _id name
  151. parent { _id name }
  152. subCategories { _id name }
  153. goods { _id name price description
  154. images { url }
  155. }
  156. }
  157. }`;
  158. return gql("http://shop-roles.node.ed.asmer.org.ua/graphql", catQuery, { q: `[{\"_id\": \"${id}\"}]` });
  159. }
  160. const actionCategoryFindOne = (id) =>
  161. actionPromiseGql('catFindOne', gqlCategoryFindOne(id));
  162. const gqlGoodFindOne = (id) => {
  163. const catQuery = `
  164. query GoodFindOne($q: String) {
  165. GoodFindOne(query: $q) {
  166. _id name price description
  167. images {
  168. url
  169. }
  170. }
  171. }
  172. `;
  173. return gql("http://shop-roles.node.ed.asmer.org.ua/graphql", catQuery, { q: `[{\"_id\": \"${id}\"}]` });
  174. }
  175. const actionGoodFindOne = (id) =>
  176. actionPromiseGql('goodsFindOne', gqlGoodFindOne(id));
  177. //////////////////////////////////
  178. const actionLogin = (login, password) => {
  179. const upsertQuery = `query login($login:String, $password:String){
  180. login(login:$login, password:$password)
  181. }`;
  182. return gql("http://shop-roles.node.ed.asmer.org.ua/graphql", upsertQuery, { login: login, password: password });
  183. }
  184. const actionFullLogin = (login, password) => {
  185. return gqlFullLogin = async (dispatch) => {
  186. //dispatch возвращает то, что вернул thunk, возвращаемый actionLogin, а там промис,
  187. //так как actionPromise возвращает асинхронную функцию
  188. let promiseResult = dispatch((dispatch) => actionLogin(login, password));
  189. let res = await promiseResult;
  190. if (res && res.data) {
  191. let token = Object.values(res.data)[0];
  192. if (token && typeof token == 'string')
  193. return dispatch(actionAuthLogin(token));
  194. }
  195. //проверьте что token - строка и отдайте его в actionAuthLogin
  196. }
  197. }
  198. ////////////////////////////////////////
  199. const actionUpsert = (login, password) => {
  200. const loginQuery = `mutation UserRegistration($login: String, $password: String) {
  201. UserUpsert(user: {login: $login, password: $password}) {
  202. _id createdAt
  203. }
  204. }`;
  205. return gql("http://shop-roles.node.ed.asmer.org.ua/graphql", loginQuery, { login: login, password: password });
  206. }
  207. const actionFullUpsert = (login, password) => {
  208. return gqlFullUpsert = async (dispatch) => {
  209. //dispatch возвращает то, что вернул thunk, возвращаемый actionLogin, а там промис,
  210. //так как actionPromise возвращает асинхронную функцию
  211. let promiseResult = dispatch((dispatch) => actionUpsert(login, password));
  212. let res = await promiseResult;
  213. dispatch(actionFullLogin(login, password));
  214. //проверьте что token - строка и отдайте его в actionAuthLogin
  215. }
  216. }
  217. ////////////////////////////////////////
  218. const store = createStore(combineReducers({ promiseReducer, authReducer }));
  219. store.subscribe(() => {
  220. console.log(store.getState())
  221. });
  222. //store.dispatch(actionRootCats());
  223. //store.dispatch(actionCategoryFindOne("6262ca7dbf8b206433f5b3d1"));
  224. //store.dispatch(actionGoodFindOne("62d3099ab74e1f5f2ec1a125"));
  225. //store.dispatch(actionFullLogin("Berg", "123456789"));
  226. //store.dispatch(actionFullUpsert("Berg1", "12345678911"));
  227. </script>
  228. </body>