Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- Не соглашусь полностью. Я написал, что промисы как раз позволяют избежать callback hell при правильном применении. И если в примере кода выше этот hell получается, то проблема, скорее всего, не в промисах.
- Ну к примеру, возьмем этот код:
- user.validate({ skip: ....}).then(() => {
- return User.find({ where: ... }).then(u => {
- ...
- return bcrypt.compare(...).then(r => {
- ...
- });
- });
- }).catch(err => {
- ...
- });
- Первое, что бросается в глаза - многое можно вынести в отдельные функции. Например, поиск пользователя по логину и паролю - логично вынести в модуль авторизации:
- userPromise = authService.findOneByLogin(email, password);
- Я это и назвал "портянкой", то, что часть кода так и просит, чтобы его вынесли в отдельные функции.
- Но, допустим, вынести код нельзя. Даже в этом случае его можно перестроить на последовательный, а не вложенный, код:
- var skip = ...;
- var userData = user.validate({ skip: skip }).then(function () {
- return User.find({ where: email });
- }).then(function (u) {
- if (!u) {
- // Это код 404, а не 204. 204 обозначает успех операции, а не ошибку.
- throw new Http404Error(...);
- }
- return [u, authService.checkPassword(u, password)];
- }).then(function (checkResult) {
- [u, isPassValid] = checkResult;
- if (!isPassValid) {
- throw new Http403Error(...);
- }
- return [u, signUserTokenAsync(u)];
- });
- // Вся работа с res в одном месте, а не раскидана по коду
- responsePromise.then(function (userData) {
- [u, jwt] = userData;
- res.status(200);
- res.json(u.getPublic());
- res.cookie(....);
- }, function (err) {
- if (err instanceof ValidationError) {
- res.status(400);
- } else if (err instance of HttpError) {
- ...
- } else if (err instance of PasswordInvalidError) {
- res.status(403);
- } else {
- console.error(err);
- res.status(503);
- }
- });
- Цепочка промисов - это процесс преобразования данных, где каждый коллбек делает одно преобразование:
- данные из Request -> успешный результат валидации -> объект User -> User + результат проверки пароля -> User + токен
- При использовании промисов мы сначала составляем цепочку преобразований, а потом реализуем ее в коде - и получается последовательный код.
- Передача массивов выглядит немного коряво и намекает на неправильное использование промисов, потому я попробовал написать альтернативный вариант, без нее, где мы делаем независимые цепочки промисов для преобразования каждого значения отдельно:
- Email -> валидированный Email -> объект User
- Password, User -> результат проверки пароля
- User -> подписанный токен
- За счет этого мы даже можем выполнить 2 последних операции параллельно (проверка пароля и генерация токена), при условии что там асинхронный код и при условии, что это не создает нежелательных эффектов (например, лишние записи в БД):
- var validatedUser = user.validate(...).then(function () {
- return User.find(where);
- });
- var isPassValidPromise = validatedUser.then(function (u) {
- return authService.checkPassword(u, password);
- });
- var signedTokenPromise = validatedUser.then(function (u) {
- return signTokenAsync(u);
- });
- Promise.all([validatedUser, isPassValidPromise, signedTokenPromise]).then(function (all) {
- [u, isPassValid, signedToken] = all;
- res.send(...);
- res.json(...);
- res.cookie(...);
- }).catch(function (err) {
- // обработка ошибок
- });
- Мне кажется, так получается правильнее. Промис - это значение, к которому применяются преобразования, а не средство для создания лестниц из кода. Если нам нужно несколько значений, мы делаем несколько цепочек промисов.
- Цепочки промисов удобно рисовать с помощью квадратиков и стрелочек на бумаге или с помощью утилиты 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
- На графе узлы обозначают значения, а стрелочки - преобразования.
- Разумеется, этот код можно переписать на await и он станет еще лучше. Но рисование цепочек поможет спроектировать код и в этом случае. Только после того, как я придумал эти цепочки, я увидел, что можно часть операций делать параллельно.
- Использовать в цепочке промисов return res.status(...) тут не получится, так как оно продолжит выполнение цепочки и скорее всего вызовет ошибку. Потому я использовал исключения для прерывания цепочки (альтернативные идеи принимаются).
- Также, в твоем коде бросается в глаза "проглатывание" ошибок: при возникновении ошибки в цепи промисов она не логгируется, а просто отдается ответ с кодом 500. Разработчик о ней не узнает, и никто не узнает. Это, на мой взгляд, неправильно, и усложняет поддержку кода. Должно быть так:
- user.validate(...).then(function () {
- ....
- }).catch(function (err) {
- if (err instanceof ValidationError) {
- // мы ловим исключение от user.validate(), хотя тут поймается и исключение
- // от других элементов цепочки, и это плохо
- return res.sendStatus(400);
- }
- // Логгируем ошибку
- loggerSevrice.logError(err);
- res.sendStatus(500);
- });
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement