У меня есть маршрут API, который реорганизуется, чтобы использовать ES6 обещания, чтобы избежать аддона обратного вызова.
После успешной конвертации в цепочку обещаний я хотел экспортировать свои функции .then()
в отдельный файл для чистоты и ясности.
Файл маршрута:
Файл функций:
Это прекрасно работает. Однако то, что я хотел бы сделать, это переместить функции, объявленные в функции класса constructor()
в независимые методы, которые могут ссылаться на значения, созданные конструктором. Таким образом, все это выглядит лучше.
Но когда я это делаю, я сталкиваюсь с проблемами обзора - this
не определено и т.д. Каков правильный способ сделать это? Является ли ES6 соответствующим для использования здесь, или я должен использовать какую-либо другую структуру?
КОД RAW:
маршрут...
.post((req, res) => {
let SubmitRouteFunctions = require('./functions/submitFunctions.js');
let fn = new SubmitRouteFunctions(req, res);
// *******************************************
// ***** THIS IS WHERE THE MAGIC HAPPENS *****
// *******************************************
Promise.all([fn.redundancyCheck, fn.getLocationInfo])
.then(fn.resetRedundantID)
.then(fn.constructSurveyResult)
.then(fn.storeResultInDB)
.then(fn.redirectToUniqueURL)
.catch((err) => {
console.log(err);
res.send("ERROR SUBMITTING YOUR RESULT: ", err);
});
})
экспортируемые функции...
module.exports = class SubmitRouteFunctions {
constructor (req, res) {
this.res = res;
this.initialData = {
answers : req.body.responses,
coreFit : req.body.coreFit,
secondFit : req.body.secondFit,
modules : req.body.modules,
};
this.newId = shortid.generate();
this.visitor = ua('UA-83723251-1', this.newId, {strictCidFormat: false}).debug();
this.clientIp = requestIp.getClientIp(req);
this.redundancyCheck = mongoose.model('Result').findOne({quizId: this.newId});
this.getLocationInfo = request.get('http://freegeoip.net/json/' + this.clientIp).catch((err) => err);
this.resetRedundantID = ([mongooseResult, clientLocationPromise]) => {
console.log(mongooseResult);
if (mongooseResult != null) {
console.log('REDUNDANT ID FOUND - GENERATING NEW ONE')
this.newId = shortid.generate();
this.visitor = ua('UA-83723251-1', this.newId, {strictCidFormat: false});
console.log('NEW ID: ', this.newId);
};
return clientLocationPromise.data;
}
this.constructSurveyResult = (clientLocation) => {
let additionalData = {quizId: this.newId, location: clientLocation};
return Object.assign({}, this.initialData, additionalData);
}
this.storeResultInDB = (newResult) => mongoose.model('Result').create(newResult).then((result) => result).catch((err) => err);
this.redirectToUniqueURL = (mongooseResult) => {
let parsedId = '?' + queryString.stringify({id: mongooseResult.quizId});
let customUrl = 'http://explore-your-fit.herokuapp.com/results' + parsedId;
this.res.send('/results' + parsedId);
}
}
}
АЛЬТЕРНАТИВА № 1:
Вместо использования классов ES6 альтернативный способ выполнения такого же поведения, который немного очищает код, заключается в экспорте анонимной функции, описанной здесь пользователем Nick Panov: В Node.js, как мне включить функции из моего другие файлы?
ФАЙЛ ФУНКЦИЙ:
module.exports = function (req, res) {
this.initialData = {
answers : req.body.responses,
coreFit : req.body.coreFit,
secondFit : req.body.secondFit,
modules : req.body.modules,
};
this.newId = shortid.generate();
this.visitor = ua('UA-83723251-1', this.newId, {strictCidFormat: false}).debug();
this.clientIp = requestIp.getClientIp(req);
this.redundancyCheck = mongoose.model('Result').findOne({quizId: this.newId});
this.getLocationInfo = request.get('http://freegeoip.net/json/' + this.clientIp).catch((err) => err);
this.resetRedundantID = ([mongooseResult, clientLocationPromise]) => {
if (mongooseResult != null) {
console.log('REDUNDANT ID FOUND - GENERATING NEW ONE')
this.newId = shortid.generate();
this.visitor = ua('UA-83723251-1', this.newId, {strictCidFormat: false});
console.log('NEW ID: ', this.newId);
};
return clientLocationPromise.data;
}
this.constructSurveyResult = (clientLocation) => {
let additionalData = {quizId: this.newId, location: clientLocation};
return Object.assign({}, this.initialData, additionalData);
}
this.storeResultInDB = (newResult) => mongoose.model('Result').create(newResult).then((result) => result).catch((err) => err);
this.redirectToUniqueURL = (mongooseResult) => {
let parsedId = '?' + queryString.stringify({id: mongooseResult.quizId});
let customUrl = 'http://explore-your-fit.herokuapp.com/results' + parsedId;
res.send('/results' + parsedId);
}
}
Хотя это не this.someFn()...
каждому методу this.someFn()...
, как я и хотел, он делает дополнительный шаг в файле маршрутизации - это делает так, что мне не нужно назначать определенное пространство имен методы.
МАРШРУТЫ
.post((req, res) => {
require('./functions/submitFunctions_2.js')(req, res);
Promise.all([redundancyCheck, getLocationInfo])
.then(resetRedundantID)
.then(constructSurveyResult)
.then(storeResultInDB)
.then(redirectToUniqueURL)
.catch((err) => {
console.log(err);
res.send("ERROR SUBMITTING YOUR RESULT: ", err);
});
})
Функции сбрасываются, чтобы отражать каждый новый объект req
и res
поскольку запросы POST попадают в маршрут, и this
ключевое слово, по-видимому, связано с обратным вызовом маршрута POST в каждом из импортированных методов.
ВАЖНОЕ ПРИМЕЧАНИЕ. Вы не можете экспортировать функцию стрелки с помощью этого метода. Экспортированная функция должна быть традиционной анонимной функцией. Вот почему, за Udo G комментируют ту же тему:
Стоит отметить, что это работает, потому что
this
в функции является глобальной областью, когда функция вызывается непосредственно (никак не привязана).
АЛЬТЕРНАТИВА № 2:
Другой вариант, любезно предоставленный Берги из: Как использовать функции стрелок (public class fields) в качестве методов класса?
То, что я ищу, действительно, является экспериментальной особенностью....
Существует предложение, которое может позволить вам опустить конструктор() и напрямую помещать присвоение в область класса с одинаковой функциональностью, но я бы не рекомендовал использовать это как очень экспериментальное.
Тем не менее, есть еще способ разделить методы:
Кроме того, вы всегда можете использовать.bind, который позволяет объявить метод в прототипе, а затем привязать его к экземпляру в конструкторе. Этот подход обладает большей гибкостью, поскольку позволяет модифицировать метод извне вашего класса.
На примере Берги:
module.exports = class SomeClass {
constructor() {
this.someMethod= this.someMethod.bind(this);
this.someOtherMethod= this.someOtherMethod.bind(this);
…
}
someMethod(val) {
// Do something with val
}
someOtherMethod(val2) {
// Do something with val2
}
}
Очевидно, что это больше соответствует тому, что я изначально искал, так как он повышает общую читаемость экспортируемого кода. НО для этого потребуется, чтобы вы назначили пространство имен новому классу в файле маршрутов, как я сделал изначально:
let SubmitRouteFunctions = require('./functions/submitFunctions.js');
let fn = new SubmitRouteFunctions(req, res);
Promise.all([fn.redundancyCheck, fn.getLocationInfo])
.then(...)
ПРЕДЛАГАЕМАЯ/ЭКСПЕРИМЕНТАЛЬНАЯ ХАРАКТЕРИСТИКА:
Это не моя рулевая рубка, но на Берги в настоящее время есть предложение Stage-2 (https://github.com/tc39/proposal-class-public-fields), которое пытается получить "поля экземпляра класса", добавленные к следующей спецификации ES.
"Поля экземпляра класса" описывают свойства, предназначенные для существования в экземплярах класса (и могут необязательно включать выражения инициализатора для указанных свойств)
Насколько я понимаю, это полностью разрешит проблему, описанную здесь , путем предоставления методам, привязанным к объектам class
ссылки на каждое его создание. Поэтому this
проблемы исчезнут, и методы могут быть необязательно связаны автоматически.
Мое (ограниченное) понимание состоит в том, что функция стрелки будет использоваться для выполнения этого, например:
class SomeClass {
constructor() {...}
someMethod (val) => {
// Do something with val
// Where 'this' is bound to the current instance of SomeClass
}
}
По-видимому, это можно сделать теперь, используя компилятор Babel, но явно экспериментальный и рискованный. Плюс, в этом случае мы пытаемся сделать это в Node/Express, что делает это практически спорным моментом :)
this.method = ...
и, возможно, менее читабельно в экспортированном файле, оно делает цепочку обещаний в вашем экспресс-маршруте чистой, ясной и лаконичной - что было моей основной задачей цель на первом месте. Он также использует меньше кода, поскольку полностью исключает функциюconstructor()
, не требует нескольких строк объявлений.bind(this)
и устраняет необходимость связывать экспортируемые методы с их собственным пространством имен. Мысли?