Итак, у меня есть приложение Express, которое использует промежуточное программное обеспечение для анализа запросов JSON POST и заполнения объекта req.body. Затем у меня есть цепочка обещаний, которая проверяет данные по схеме с помощью Joi, а затем сохраняет их в базе данных.

Я хотел бы проверить, возникла ли ошибка после одного из этих процессов, обработать ее соответствующим образом, отправив код состояния, затем ПОЛНОСТЬЮ ОТМЕНИТЬ цепочку обещаний. Я чувствую, что должен быть какой-то ЧРЕЗВЫЧАЙНО ЧИСТЫЙ И ПРОСТОЙ способ сделать это (возможно, какое-то заявление о разрыве?), Но я нигде не могу его найти. Вот мой код. Я оставил комментарии, показывающие, где я надеюсь прервать цепочку обещаний.

const joi = require("joi");

const createUserSchema = joi.object().keys({
    username: joi.string().alphanum().min(4).max(30).required(),
    password: joi.string().alphanum().min(2).max(30).required(),
});

//Here begins my promise chain 
app.post("/createUser", (req, res) => {
    //validate javascript object against the createUserSchema before storing in database
    createUserSchema.validate(req.body)
        .catch(validationError => {
           res.sendStatus(400);

           //CLEANLY ABORT the promise chain here

           })
        .then(validatedUser => {
            //accepts a hash of inputs and stores it in a database 
            return createUser({
                    username: validatedUser.username,
                    password: validatedUser.password
                })
        .catch(error => {
            res.sendStatus(500);

            //CLEANLY ABORT the promise chain here

        })
        //Only now, if both promises are resolved do I send status 200
        .then(() => {
            res.sendStatus(200); 
            }                 
        )

});
4
Joshua Avery 21 Авг 2018 в 07:39

4 ответа

Лучший ответ

Вы не можете прервать цепочку обещаний в середине. Он будет вызывать .then() или .catch() позже в цепочке (при условии, что есть и то, и другое, и при условии, что ваши обещания разрешены или отклонены).

Обычно, как вы справляетесь с этим, вы помещаете один .catch() в конец цепочки, и он проверяет тип ошибки и предпринимает соответствующие действия. Вы не обрабатываете ошибку ранее в цепочке. Вы позволяете последним .catch() обрабатывать вещи.

Вот что я бы посоветовал:

// helper function
function err(status, msg) {
    let obj = new Error(msg);
    obj.status = status;
    return obj;
}

//Here begins my promise chain 
app.post("/createUser", (req, res) => {
    //validate javascript object against the createUserSchema before storing in database
    createUserSchema.validate(req.body).catch(validationError => {
        throw err("validateError", 400)
    }).then(validatedUser => {
            //accepts a hash of inputs and stores it in a database 
            return createUser({
                    username: validatedUser.username,
                    password: validatedUser.password
            }).catch(err => {
                throw err("createUserError", 500);
            });
    }).then(() => {
        // success
        res.sendStatus(200); 
    }).catch(error => {
        console.log(error);
        if (error && error.status) {
            res.sendStatus(error.status);
        } else {
            // no specific error status specified
            res.sendStatus(500);
        }
    });
});

Это дает несколько преимуществ:

  1. Любая ошибка распространяется на последний .catch() в конце цепочки, где она регистрируется, и соответствующий код отправляется только в одном месте кода.
  2. Успех обрабатывается только в одном месте, где этот статус отправляется.
  3. Это бесконечно расширяемо для большего количества звеньев в цепочке. Если у вас есть больше операций, которые могут иметь ошибки, они могут «прервать» остальную часть цепочки (кроме последней .catch(), просто отклонив ее с соответствующим объектом ошибки).
  4. Это несколько похоже на практику разработки, заключающуюся в том, чтобы не иметь много выражений return value по всей вашей функции, а скорее накапливать результат и затем возвращать его в конце, что некоторые люди считают хорошей практикой для сложной функции.
  5. При отладке вы можете установить точки останова в одной .then() и одной .catch(), чтобы увидеть окончательное разрешение цепочки обещаний, поскольку вся цепочка проходит либо через последний .then(), либо последний {{X3} } .
3
jfriend00 21 Авг 2018 в 06:34

Одна стратегия состоит в том, чтобы отделить обработку ошибок в подпроцессах, которые имеют свою индивидуальную обработку ошибок. Если вы выбросите из них ошибку, вы обойдете основную цепочку обещаний.

Что-то типа:

return Promise.resolve().then(() => {
  return createUserSchema.validate(req.body)
    .catch(validationError => {
       res.sendStatus(400);
       throw 'abort';
    });
}).then(validatedUser => {
   // if an error was thrown before, this code won't be executed
   // accepts a hash of inputs and stores it in a database 
   return createUser({
      username: validatedUser.username,
      password: validatedUser.password
   }).catch(error => {
      // if an error was previously thrown from `createUserSchema.validate`
      // this code won't execute

      res.sendStatus(500);
      throw 'abort';
   });
}).then(() => {
   // can put in even more code here
}).then(() => {
  // it was not aborted
  res.sendStatus(200); 
}).catch(() => {
  // it was aborted
});

Вы можете пропустить упаковку Promise.resolve().then(), но она включена в иллюстративных целях общего шаблона разделения каждой задачи и обработки ошибок.

0
Steve 21 Авг 2018 в 05:18

.catch возвращает разрешенное обещание по умолчанию. Вы хотите отклоненный Promsise . Итак, вы должны return отклонить обещание изнутри .catch, чтобы будущие .then не выполнялись:

     .catch(validationError => {
       res.sendStatus(400);
       return Promise.reject();
     })

Но обратите внимание, что это приведет к предупреждению консоли:

Uncaught (в обещании) ...

Поэтому было бы неплохо добавить еще один .catch в конец, чтобы подавить ошибку (а также перехватить любые другие ошибки, которые появляются):

const resolveAfterMs = ms => new Promise(res => setTimeout(() => {
  console.log('resolving');
  res();
}), ms);

console.log('start');
resolveAfterMs(500)
  .then(() => {
    console.log('throwing');
    throw new Error();
  })
  .catch(() => {
    console.log('handling error');
    return Promise.reject();
  })
  .then(() => {
    console.log('This .then should never execute');
  })
  .catch(() => void 0);

Если вы хотите избежать всех будущих .then s и future .catch es, я полагаю, вы могли бы вернуть Promise, который никогда не разрешается, хотя на самом деле это не так звучит как знак хорошо разработанной кодовой базы:

const resolveAfterMs = ms => new Promise(res => setTimeout(() => {
  console.log('resolving');
  res();
}), ms);

console.log('start');
resolveAfterMs(500)
  .then(() => {
    console.log('throwing');
    throw new Error();
  })
  .catch(() => {
    console.log('handling error');
    return new Promise(() => void 0);
  })
  .then(() => {
    console.log('This .then should never execute');
  })
  .catch(() => {
    console.log('final catch');
  });
1
CertainPerformance 21 Авг 2018 в 04:54

Более чистое решение, которое вы пытаетесь выполнить, может заключаться в использовании экспресс-проверки, это простая оболочка для joi, которая предоставляет вам express middleware для проверки тела, параметров, запросов, заголовков и файлов cookie экспресс-запроса на основе вашей схемы Joi.

Таким образом, вы можете просто обрабатывать любые ошибки проверки Joi, возникающие в промежуточном программном обеспечении в вашем «универсальном» экспрессе обработчик ошибок, с чем-то вроде:

const ev = require('express-validation');
app.use(function (err, req, res, next) {
  // specific for validation errors
  if (err instanceof ev.ValidationError) 
     return res.status(err.status).json(err);
  ...
  ...
  ... 
}

Если вы не хотите использовать пакет экспресс-проверки, вы можете написать свой собственное простое промежуточное ПО, которое выполняет более или менее то же самое, как описано здесь (см. пример здесь).

1
Yoni Rabinovitch 21 Авг 2018 в 07:22
51941655