Получение сведений о пользователе, сообщения и комментарии, используя обещания в ExpressJS


Я создаю сервис API на NodeJS с использованием Express (версия 4) рамки. Создали архитектуру MVC для же (нет мнений, только модель, и контроллер, так как это API-интерфейс).

Кроме того, есть маршруты. Когда запрос сделан, маршрутизатор перенаправляет запрос к функции контроллера и оттуда функции, написанные в модели называются и ответ возвращается пользователю в формате JSON.

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

Мой NodeJS версии 6.12 и он не поддерживает асинхронный/ждут.

Я хочу убедиться, что путь я реализовал обещания верны.

Вот пример функции из моего контроллера, с помощью которого я загружаю данные пользователя. Пользователь передает идентификатор пользователя и используя, что я приведу информацию о пользователе. И используя детали от 1-го асинхронный запрос, я приведу посты пользователя и после этого, я приведу комментарии пользователей. Я хочу убедиться, что моя реализация-это правильно.

Примечание: Я убрал обработку ошибок от некоторых асинхронных запросов, чтобы сделать код более понятным.

exports.fetchUserInformation = function (req, res) {
  var response = {};

  if (req.query.userId) {
    var userId = req.query.userId;
    response['userId'] = userId;
    new Promise(function (resolve, reject) {
      var param = {
        userId: userId
      };
      //1 - Call to Model function to fetch user info
      User.getUserDetails(param, function (err, rows) {
        if (err) {
          reject(err);
        } else {
          resolve(rows);
        }
      });
    }).then(function (result1) {
      var result2;
      response['userDetails'] = result1;

      //Pass result from previous promise to fetch next result
      var params1 = {
        userId: userId,
        result: result1
      };
      //2 - Call to Model function to fetch user timeline posts
      User.fetchUserPosts(params1, function (err, rows) {
        result2 = rows;
      });
    }).then(function (result2) {
      response['userPosts'] = result2;

      var params2 = {
        userId: userId,
        userType: 'user'
      };
      User.fetchUserComments(params2, function (err, rows) {

        response['userComments'] = rows;

        //return the response to user
        res.json({
          success: true,
          response: response
        });

      });
    }).catch(function (error) {
      //error logging starts
      var logData = {
        stackTrace: error
      };
      logHlpr.logThisError(logData); //calling another function to log data
      //error logging ends

      res.json({
        success: false
      });
    });
  }
};

У меня несколько сомнений:

Я делаю 3 асинхронных запросов к модели - User.getUserDetails, User.fetchUserPosts, User.fetchUserComments используя одно обещание. Я должен реализовать 3 обещания?

Я довольно новичок в NodeJS и обещаний.



Комментарии
3 ответа

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

Если вы создаете 3 обещания, все это можно выполнить параллельно. Поэтому ответ-да, вы должны реализовать с 3 обещает.

Кстати, с помощью "синей птице" библиотеки, вы можете писать более читаемый JS-код, как я рефакторинг показано ниже, (я объясню подробнее):

const bluebird = require('bluebird')
const User = require('./model/User')
bluebird.promisifyAll(User)

function asyncHandler(fn) {
const promiseFn = bluebird.method(fn)
return function(req, res) {
let resultPromise = promiseFn.call(this, req,res)
resultPromise.then(function(result) {
res.json({
success: true,
response: result,
})
}).catch(function(err) {
logHlpr.logThisError({stackTrace: err});
res.json({
success: false,
})
})
}
}

exports.fetchUserInformation = asyncHandler(function(req, res) {
if (!req.query.userId) {
throw new Error("UserId is required")
}
let userId = req.query.userId
let userDetails = User.getUserDetailsAsync({userId: userId})
let userPosts = userDetails.then(result => User.fetchUserPostsAsync({userId: userId, result: result}))
let userComments = User.fetchUserCommentsAsync({userId: userId, userType: 'user'})
return bluebird.props({
userId: userId,
userDetails: userDetails,
userPosts: userPosts,
userComments: userComments,
})
})

Большую часть времени, когда вы пишете JavaScript в Node.js вы должны работать с обратных вызовов, так что конвертировать все отзвонились-типа обещать-стиль с помощью bluebird.promisifyAll() или bluebird.promisify() позволит вам работать с кодом проще, см. Блюберд документ.

После этого большинство из функции обработчика вы пишете расправится с обещают, так что вы можете создать функцию высшего порядка, как asyncHandler, которые посылают успеха ответ в случае, если обещание выполнено. Вы можете также двигаться asyncHandler функция общая-файл и require() позже его использовать в нескольких местах.

Для функции обработчика fetchUserInformation в коде образца, вы хотите вернуть результат в виде объекта. Этот объект содержит в результате много обещаний. Вы можете использовать bluebird.props() которые создают оболочку обещают, что ждать все объектные значения должны быть выполнены.

Последние вещи, я бы посоветовал вам найти способ, чтобы переопределить User.fetchUserPosts() что это не требуется userDetails как часть параметра, поэтому этот метод может быть выполнена без ожидания User.getUserDetails() результат и может сделать код более элегантным.

4
ответ дан 10 марта 2018 в 01:03 Источник Поделиться

Да, вы должны реализовать три отдельных обещаний и их последовательности для достижения желаемого эффекта.

Вы не спровоцировав новый асинхронный запрос на обратный вызов резолюции первого и второго обещания. Это создает такой же обратного вызова ад, обещает предназначены, чтобы избежать.

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

Пожалуйста, см. Этот учебник для кода образцов.

Привет, Имярек, я видел ваш рефакторинг кода. Он по-прежнему является пирамидальной и не плоская. Более емкий пример кода, с помощью ванили на JavaScript обещания могут быть:

exports.fetchUserInformation = function (req, res) {
var response = {};
if (req.query.userId) {
var userId = req.query.userId;
response['userId'] = userId;

var getUserDetails = function(userId){ //returns first promise
return new Promise(function (resolve, reject) {
var param = {
userId: userId
};

User.getUserDetails(param, function (err, rows) {
if (err) {
reject(err);
} else {
resolve(rows); //rows will be available for second promise to use as result1
}
});
});
};

var getUserPosts = function(result1){ //returns second promise
return new Promise(function (resolve, reject) {
var result2;
response['userDetails'] = result1;

//Pass result from previous promise to fetch next result
var params1 = {
userId: userId,
result: result1
};
//2 - Call to Model function to fetch user timeline posts
User.fetchUserPosts(params1, function (err, rows) {
if (err) {
reject(err);
} else {
resolve(rows); //rows will be available for third promise to use as result2
}
});
});
};

var fetchUserComments = function(result2){ //returns third promise
return new Promise(function (resolve, reject) {
response['userPosts'] = result2;

var params2 = {
userId: userId,
userType: 'user'
};
User.fetchUserComments(params2, function (err, rows) {
if (err) {
reject(err);
} else {
response['userComments'] = rows;
resolve(response); //response will be available for "resolve " function
}
});
});
};

var getUserData = function(userId){
getUserDetails(userId)
.then(getUserPosts) // promise chaining
.then(fetchUserComments) // previously resolved variables are available for this promise
.then(function (response){
res.json({
success: true,
response: response
});
})
.catch( function (err){
console.log(err);
var logData = {
stackTrace: err
};
logHlpr.logThisError(logData); //calling another function to log data
//error logging ends

res.json({
success: false
});
});
}

getUserData(userId); // main function call which triggers all the promises
}
};

Вы можете также использовать другие обещают библиотек, таких, как вопрос или Блюберд.

2
ответ дан 7 марта 2018 в 02:03 Источник Поделиться

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


  • обработка случае if (req.query.userId) Это ложь (или бросков);

  • уплощение цепи обещаю

  • прохождение response от этапа к этапу добавления свойств, как она идет

  • имея один .catch() в конце.

Например, можно написать :

exports.fetchUserInformation = function(req, res) {
if(!(req.query && req.query.userId)) {
return Promise.reject(new TypeError('request did not have the expected properties'));
}
// Stage 1 - Call to Model function to fetch user info
return new Promise(function(resolve, reject) {
var response = { 'userId': req.query.userId }; // embryonic response object
User.getUserDetails(response, function(err, rows) {
if(err) {
reject(err);
} else {
response.userDetails = rows; // add .userDetails property to the response object ...
resolve(response); // ... and pass to the next step in the chain
}
});
})
.then(function(response) {
// Stage 2 - Call to Model function to fetch user timeline posts
return new Promise((resolve, reject) {
User.fetchUserPosts({ 'userId': response.userId, 'result': response.userDetails }, function(err, rows) {
if(err) {
reject(err);
} else {
response.userPosts = rows; // add .userPosts property to the response object ...
resolve(reponse); // ... and pass to the next step in the chain
}
});
});
})
.then(function(response) {
// Stage 3 - Fetch user comments
return new Promise(function(resolve, reject) {
User.fetchUserComments({ 'userId': response.userId, 'userType': 'user' }, function(err, rows) {
if(err) {
reject(err);
} else {
response.userComments = rows; // add .userComments property to the response object ...
resolve(response); // ... and pass to the next step in the chain
}
});
});
})
.then(function(response) {
// compose and deliver the full response
res.json({
'success': true,
'response': response
});
})
.catch(function(error) {
logHlpr.logThisError({
'stackTrace': error
});
res.json({ 'success': false });
});
};

Чтобы упростить код, вы могли бы избежать необходимости new Promise() С помощью Блюберд обещание либерал-это обещание.promisifyAll().

Например, с Блюберд установлен, вы могли бы написать :

exports.fetchUserInformation = function(req, res) {
if(!(req.query && req.query.userId)) {
return Promise.reject(new TypeError('request did not have the expected properties'));
}
// Stage 1 - Fetch user info
return User.getUserDetailsAsync({ 'userId': req.query.userId }) // call the promisified version of User.getUserDetails()
.then(function(rows) {
return { 'userId': req.query.userId, 'userDetails': rows }; // create embryonic response and pass to the next step in the chain
});
.then(function(response) {
// Stage 2 - Fetch user timeline posts
return User.fetchUserPostsAsync({ 'userId': response.userId, 'result': response.userDetails }) // call the promisified version of User.fetchUserPosts()
.then(function(rows) {
response.userPosts = rows; // add .userPosts property to the response object ...
return reponse; // ... and pass to the next step in the chain
});
})
.then(function(response) {
// Stage 3 - Fetch user comments
return User.fetchUserCommentsAsync({ 'userId': response.userId, 'userType': 'user' }) // call the promisified version of User.fetchUserComments()
.then(function(rows) {
response.userComments = rows; // add .userComments property to the response object ...
return response; // ... and pass to the next step in the chain
});
})
.then(function(response) {
// compose and deliver the full response
res.json({
'success': true,
'response': response
});
})
.catch(function(error) {
logHlpr.logThisError({
'stackTrace': error
});
res.json({ 'success': false });
});
};

2
ответ дан 9 марта 2018 в 03:03 Источник Поделиться