Advertisement
Guest User

Untitled

a guest
Jan 16th, 2019
140
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 7.94 KB | None | 0 0
  1. Не соглашусь полностью. Я написал, что промисы как раз позволяют избежать callback hell при правильном применении. И если в примере кода выше этот hell получается, то проблема, скорее всего, не в промисах.
  2.  
  3. Ну к примеру, возьмем этот код:
  4.  
  5. user.validate({ skip: ....}).then(() => {
  6. return User.find({ where: ... }).then(u => {
  7. ...
  8. return bcrypt.compare(...).then(r => {
  9. ...
  10. });
  11. });
  12. }).catch(err => {
  13. ...
  14. });
  15.  
  16. Первое, что бросается в глаза - многое можно вынести в отдельные функции. Например, поиск пользователя по логину и паролю - логично вынести в модуль авторизации:
  17.  
  18. userPromise = authService.findOneByLogin(email, password);
  19.  
  20. Я это и назвал "портянкой", то, что часть кода так и просит, чтобы его вынесли в отдельные функции.
  21.  
  22. Но, допустим, вынести код нельзя. Даже в этом случае его можно перестроить на последовательный, а не вложенный, код:
  23.  
  24. var skip = ...;
  25. var userData = user.validate({ skip: skip }).then(function () {
  26. return User.find({ where: email });
  27. }).then(function (u) {
  28. if (!u) {
  29. // Это код 404, а не 204. 204 обозначает успех операции, а не ошибку.
  30. throw new Http404Error(...);
  31. }
  32.  
  33. return [u, authService.checkPassword(u, password)];
  34. }).then(function (checkResult) {
  35. [u, isPassValid] = checkResult;
  36.  
  37. if (!isPassValid) {
  38. throw new Http403Error(...);
  39. }
  40.  
  41. return [u, signUserTokenAsync(u)];
  42. });
  43.  
  44. // Вся работа с res в одном месте, а не раскидана по коду
  45. responsePromise.then(function (userData) {
  46.  
  47. [u, jwt] = userData;
  48.  
  49. res.status(200);
  50. res.json(u.getPublic());
  51. res.cookie(....);
  52. }, function (err) {
  53. if (err instanceof ValidationError) {
  54. res.status(400);
  55. } else if (err instance of HttpError) {
  56. ...
  57. } else if (err instance of PasswordInvalidError) {
  58. res.status(403);
  59. } else {
  60. console.error(err);
  61. res.status(503);
  62. }
  63. });
  64.  
  65. Цепочка промисов - это процесс преобразования данных, где каждый коллбек делает одно преобразование:
  66.  
  67. данные из Request -> успешный результат валидации -> объект User -> User + результат проверки пароля -> User + токен
  68.  
  69. При использовании промисов мы сначала составляем цепочку преобразований, а потом реализуем ее в коде - и получается последовательный код.
  70.  
  71. Передача массивов выглядит немного коряво и намекает на неправильное использование промисов, потому я попробовал написать альтернативный вариант, без нее, где мы делаем независимые цепочки промисов для преобразования каждого значения отдельно:
  72.  
  73. Email -> валидированный Email -> объект User
  74. Password, User -> результат проверки пароля
  75. User -> подписанный токен
  76.  
  77. За счет этого мы даже можем выполнить 2 последних операции параллельно (проверка пароля и генерация токена), при условии что там асинхронный код и при условии, что это не создает нежелательных эффектов (например, лишние записи в БД):
  78.  
  79. var validatedUser = user.validate(...).then(function () {
  80. return User.find(where);
  81. });
  82.  
  83. var isPassValidPromise = validatedUser.then(function (u) {
  84. return authService.checkPassword(u, password);
  85. });
  86.  
  87. var signedTokenPromise = validatedUser.then(function (u) {
  88. return signTokenAsync(u);
  89. });
  90.  
  91. Promise.all([validatedUser, isPassValidPromise, signedTokenPromise]).then(function (all) {
  92. [u, isPassValid, signedToken] = all;
  93.  
  94. res.send(...);
  95. res.json(...);
  96. res.cookie(...);
  97. }).catch(function (err) {
  98. // обработка ошибок
  99. });
  100.  
  101. Мне кажется, так получается правильнее. Промис - это значение, к которому применяются преобразования, а не средство для создания лестниц из кода. Если нам нужно несколько значений, мы делаем несколько цепочек промисов.
  102.  
  103. Цепочки промисов удобно рисовать с помощью квадратиков и стрелочек на бумаге или с помощью утилиты Graphwiz: https://dreampuf.github.io/GraphvizOnline/#digraph%20Authorization%20%7B%0A%20%20subgraph%20cluster_0%20%7B%0A%20%20%20%20validatedEmail%20-%3E%20User%0A%20%20%20%20label%20%3D%20%22Find%20User%22%3B%0A%20%20%7D%0A%0A%20%20subgraph%20cluster_1%20%7B%0A%20%20%20%20User%20-%3E%20isPassValid%3B%0A%20%20%20%20label%20%3D%20%22Check%20Pass%22%3B%0A%20%20%7D%0A%20%20%0A%20%20subgraph%20cluster_2%20%7B%0A%20%20%20%20label%20%3D%20%22Sign%20Token%22%3B%0A%20%20%20%20User%20-%3E%20signedToken%0A%20%20%7D%0A%20%20%0A%20%20email%20-%3E%20validatedEmail%0A%20%20password%20-%3E%20isPassValid%0A%20%20%0A%20%20User%20-%3E%20successfulResponse%0A%20%20isPassValid%20-%3E%20successfulResponse%0A%20%20signedToken%20-%3E%20successfulResponse%0A%7D
  104.  
  105. На графе узлы обозначают значения, а стрелочки - преобразования.
  106.  
  107. Разумеется, этот код можно переписать на await и он станет еще лучше. Но рисование цепочек поможет спроектировать код и в этом случае. Только после того, как я придумал эти цепочки, я увидел, что можно часть операций делать параллельно.
  108.  
  109. Использовать в цепочке промисов return res.status(...) тут не получится, так как оно продолжит выполнение цепочки и скорее всего вызовет ошибку. Потому я использовал исключения для прерывания цепочки (альтернативные идеи принимаются).
  110.  
  111. Также, в твоем коде бросается в глаза "проглатывание" ошибок: при возникновении ошибки в цепи промисов она не логгируется, а просто отдается ответ с кодом 500. Разработчик о ней не узнает, и никто не узнает. Это, на мой взгляд, неправильно, и усложняет поддержку кода. Должно быть так:
  112. user.validate(...).then(function () {
  113. ....
  114. }).catch(function (err) {
  115. if (err instanceof ValidationError) {
  116. // мы ловим исключение от user.validate(), хотя тут поймается и исключение
  117. // от других элементов цепочки, и это плохо
  118. return res.sendStatus(400);
  119. }
  120.  
  121. // Логгируем ошибку
  122. loggerSevrice.logError(err);
  123. res.sendStatus(500);
  124. });
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement