My initialize events are not running in the expected order.
I have the following lifecycle event listeners in my Bookshelf model:
initialize: function() {
this.on('saving', this.validate);
this.on('creating', this.generateId);
this.on('creating', this.hashPassword);
this.on('creating', this.generateUsername);
}
I return a Promise in the 'generateUsername' method. According to the logs, this Promise completes after the 'validate' method is run.
I was under the impression that the lifecycle events are Promise-aware and would wait for the Promise to complete before proceeding. What am I doing wrong?
Lifecycle methods:
generateId: function() {
this.set('id', shortid.generate());
},
hashPassword: function() {
/* eslint-disable no-invalid-this */
return bcrypt.genSaltAsync(12).bind(this)
.then(function(salt) {
return bcrypt.hashAsync(this.get('password'), salt);
})
.then(function(hash) {
this.set('password', hash);
});
},
generateUsername: function(model, attr, options) {
var email = this.get('email');
if (!email) {
this.set('username', shortid.generate());
return;
}
var username = email.substring(0, email.indexOf('#')).replace(/[^a-z0-9_-]/g, '');
if (!username) {
this.set('username', shortid.generate());
return;
}
var self = this;
return User.where('username', '=', username)
.fetch()
.then(function(found) {
if (!found) {
self.set('username', username);
winston.info('setting username to [%s]', username);
} else {
self.set('username', username + shortid.generate());
winston.info('setting username to [%s]', self.get('username'));
}
});
},
validate: function() {
return checkit.run(this.attributes);
}
Logs:
info: Validation errors { username: [ 'The username is required' ],
ageGroup: [ 'The ageGroup is required' ],
bodyType: [ 'The bodyType is required' ] }
info: setting username to [nim_sathiVyxIkm--Hx]
The incorrect ordering of events was reported as an issue in Bookshelf and fixed in version 0.13.0 that was released in the 18th of March, 2018, so your code should work as expected now.
Related
I am upgrading jquery and saw that the "async: false" option has been deprecated. This makes sense and in 99.9% of cases I agree with the rationale, but I have a case where I think I really need it and I cannot for the life of me figure out how to make this work with a purely async ajax call no matter how I use promises or async/await.
My use case is in a Vue component and I have an array of contacts. What I need to do is map over the contacts and validate them. One such validation requires a quick check of email validity via a "check_email" ajax endpoint.
Once I validate (or not) the list, I then submit the list (if valid) or show error messages (if invalid).
My code is something like this
sendContacts: function() {
valid = this.validateContacts()
if (valid) {
// send the contacts
} else {
return // will display error messages on contacts objects
}
},
validateContacts: function() {
this.contacts = this.contacts.map((contact) => {
if (!contact.name) {
contact.validDetails.name = false
contact.valid = false
return contact
}
if (!contact.email) {
contact.validDetails.emailExists = false
contact.valid = false
return contact
}
if (!check_email(email)) { // THIS IS ASYNC NOW WHAT DO I DO
contact.valid = false
contact.validDetails.emailFormat = false
}
return contact
}
var validData = this.contacts.map(c => {
return c.valid
})
return !validData.includes(false)
}
function check_email(email) {
const url = `/api/v1/users/check-email?email=${email}`
let valid = false
$.ajax({
url: url,
type: 'POST',
async: false, // I can't do this anymore
headers: {
'X-CSRFToken': csrfToken
},
success: resp => {
valid = true
},
error: err => {
}
})
return valid
}
my data function:
data: function() {
return {
contacts: [this.initContact()],
showThanks: false,
emailError: false,
blankEmail: false,
blankName: false
}
},
methods: {
initContact: function() {
return {
name: null,
email: null,
title: null,
validDetails: this.initValidDetails(),
valid: true,
}
},
initValidDetails: function() {
return {
emailDomain: true,
emailExists: true,
emailFormat: true,
name: true
}
}
}
Again, I have tried async/await in every place I could think of and I cannot get this to validate properly and then perform correct logic regarding whether the send contacts function part of the function should fire. Please help!
Once any part of your validation is asynchronous, you must treat the entire thing as asynchronous. This includes when calling validateContacts in sendContacts.
First, you should change check_email to return Promise<bool>. It's usually a bad idea to include jQuery in a Vue project so let's use fetch instead (Axios being another popular alternative).
async function check_email(email) {
const params = new URLSearchParams({ email })
const res = await fetch(`/api/v1/users/check-email?${params}`, {
method: 'POST',
headers: {
'X-CSRFToken': csrfToken
}
})
return res.ok
}
As for your async validation logic, it's best to map your contacts to an array of promises and wait for them all with Promise.all.
async validateContacts () {
const validationPromises = this.contacts.map(async contact => {
if (!contact.name) {
return {
...contact,
valid: false,
validDetails: {
...contact.validDetails,
name: false
}
}
}
if (!contact.email) {
return {
...contact,
valid: false,
validDetails: {
...contact.validDetails,
emailExists: false
}
}
}
if (await check_email(contact.email)) { // await here
return {
...contact,
valid: false,
validDetails: {
...contact.validDetails,
emailFormat: false
}
}
}
return { ...contact, valid: true }
})
// now wait for all promises to resolve and check for any "false" values
this.contacts = await Promise.all(validationPromises)
return this.contacts.every(({ valid }) => valid)
}
As mentioned, now you need to treat this asynchronously in sendContacts
async sendContacts () {
if (await this.validateContacts()) {
// send the contacts
}
}
I have the below code in Angular component
export class ScheduleComponent implements OnInit, OnDestroy {
source:any;
connect(dateValue){
this.source = new
EventSource('http://localhost:8080/api/schedbydate?mydate='+dateValue);
this.source.addEventListener('datarec', datarec => {
let schedule: Notification;
this.schedule = JSON.parse(datarex.data);
}, false);
}
ngOnInit() {
this._erdayService.getErday().subscribe((erday) => {
this._date = erday.text();
this._erdayService.currentMessage.subscribe(message => {
this._date = message;
this.connect(this._date);}
, (error) => { console.error('SERVER ERROR: SELECTED DAY'); });}
, (error) => { console.error('SERVER ERROR:getSchedulesByDate()'); });
}
ngOnDestroy() {
this.source.removeEventListener('message', this.message, false);
//this line doesn't work because I can't access enter variable here!
console.log("Server stopped schedule");
}
}
The issue is the this._date is initially loaded erday and UI view is according to erday. Now when I change the this._date to message, the UI view gets changed.
But still the erday data is shown in UI and the UI view fluctuates between erday & message and I'm not able to stop the this.source.addEventListener().
I tried to destroy in ngOnDestroy(),but it is not working.
I even tried this.source.close();.
Can someone help to know how to stop the listener created before calling another listener on same source ?
You subscribe to 2 data sources that emits continuously :
- The first being this._erdayService.currentMessage
- The second is this.source (when you trigger this.connect())
So this._date will change continuously. So you have to decide which data source you want to keep.
Case 1: You want to keep this.source as your data provider:
export class ScheduleComponent implements OnInit, OnDestroy {
source:any;
sourceListenerSubscription$ : Observable<any>;
connect(dateValue){
this.source = new
EventSource('http://localhost:8080/api/schedbydate?mydate='+dateValue);
this.sourceSubscription$ = Observable.fromEvent(this.source, 'datarec').subscribe( datarec => {
let schedule: Notification;
this.schedule = JSON.parse(datarex.data);
}, false);
}
ngOnInit() {
this._erdayService.getErday().subscribe((erday) => {
this._date = erday.text();
// take only one erday message, then listen to your spring server
this._erdayService.currentMessage.take(1).subscribe(message => {
this._date = message;
this.connect(this._date);}
, (error) => { console.error('SERVER ERROR: SELECTED DAY'); });}
, (error) => { console.error('SERVER ERROR:getSchedulesByDate()'); });
}
ngOnDestroy() {
this.source.removeEventListener('message', this.message, false);
//this line doesn't work because I can't access enter variable here!
console.log("Server stopped schedule");
}
}
Case 2: You want to keep erday as your data provider:
export class ScheduleComponent implements OnInit, OnDestroy {
source:any;
sourceListenerSubscription$ : Observable<any>;
connect(dateValue){
this.source = new
EventSource('http://localhost:8080/api/schedbydate?mydate='+dateValue);
// take date once from spring server, and keep erday as data source
this.sourceSubscription$ = Observable.fromEvent(this.source, 'datarec').take(1).subscribe( datarec => {
let schedule: Notification;
this.schedule = JSON.parse(datarex.data);
}, false);
}
ngOnInit() {
this._erdayService.getErday().subscribe((erday) => {
this._date = erday.text();
this._erdayService.currentMessage.subscribe(message => {
this._date = message;
this.connect(this._date);}
, (error) => { console.error('SERVER ERROR: SELECTED DAY'); });}
, (error) => { console.error('SERVER ERROR:getSchedulesByDate()'); });
}
ngOnDestroy() {
this.source.removeEventListener('message', this.message, false);
//this line doesn't work because I can't access enter variable here!
console.log("Server stopped schedule");
}
}
I'm creating a skill that will call back different incidents at different dates and times from a DynamoDB table through Alexa.
My 3 columns are data, time and incident
I've defined my partition and sort key in my Lambda function as
let GetMachineStateIntent = (context, callback) => {
var params = {
TableName: "updatedincident",
Key: {
date: "2017-03-21",
time: "07:38",
incident: "Blocked Primary",
}
};
When I try to test my skill I can't seem to recall the incident correctly, the error I'm getting in Cloudwatch is:
2018-03-28T14:48:53.397Z 042319cb-4a3e-49ae-8b33-1641367107d4 Unexpected error occurred in the skill handler! TypeError: Cannot read property 'type' of undefined
at exports.handler.e (/var/task/index.js:70:16)
as well as:
2018-03-28T14:48:53.417Z 042319cb-4a3e-49ae-8b33-1641367107d4
{
"errorMessage": "Unexpected error"
}
Here is my code from index.js
var AWSregion = 'us-east-1'; // us-east-1
var AWS = require('aws-sdk');
var dbClient = new AWS.DynamoDB.DocumentClient();
AWS.config.update({
region: "'us-east-1'"
});
let GetMachineStateIntent = (context, callback) => {
var params = {
TableName: "updatedincident",
Key: {
date: "2018-03-28",
time: "04:23",
}
};
dbClient.get(params, function (err, data) {
if (err) {
// failed to read from table for some reason..
console.log('failed to load data item:\n' + JSON.stringify(err, null, 2));
// let skill tell the user that it couldn't find the data
sendResponse(context, callback, {
output: "the data could not be loaded from your database",
endSession: false
});
} else {
console.log('loaded data item:\n' + JSON.stringify(data.Item, null, 2));
// assuming the item has an attribute called "incident"..
sendResponse(context, callback, {
output: data.Item.incident,
endSession: false
});
}
});
};
function sendResponse(context, callback, responseOptions) {
if(typeof callback === 'undefined') {
context.succeed(buildResponse(responseOptions));
} else {
callback(null, buildResponse(responseOptions));
}
}
function buildResponse(options) {
var alexaResponse = {
version: "1.0",
response: {
outputSpeech: {
"type": "SSML",
"ssml": `<speak><prosody rate="slow">${options.output}</prosody></speak>`
},
shouldEndSession: options.endSession
}
};
if (options.repromptText) {
alexaResponse.response.reprompt = {
outputSpeech: {
"type": "SSML",
"ssml": `<speak><prosody rate="slow">${options.reprompt}</prosody></speak>`
}
};
}
return alexaResponse;
}
exports.handler = (event, context, callback) => {
try {
var request = event.request;
if (request.type === "LaunchRequest") {
sendResponse(context, callback, {
output: "welcome to my skill. what data are you looking for?",
endSession: false
});
}
else if (request.type === "IntentRequest") {
let options = {};
if (request.intent.name === "GetMachineStateIntent") {
GetMachineStateIntent(context, callback);
} else if (request.intent.name === "AMAZON.StopIntent" || request.intent.name === "AMAZON.CancelIntent") {
sendResponse(context, callback, {
output: "ok. good bye!",
endSession: true
});
}
else if (request.intent.name === "AMAZON.HelpIntent") {
sendResponse(context, callback, {
output: "you can ask me about incidents that have happened",
reprompt: "what can I help you with?",
endSession: false
});
}
else {
sendResponse(context, callback, {
output: "I don't know that one! please try again!",
endSession: false
});
}
}
else if (request.type === "SessionEndedRequest") {
sendResponse(context, callback, ""); // no response needed
}
else {
// an unexpected request type received.. just say I don't know..
sendResponse(context, callback, {
output: "I don't know that one! please try again!",
endSession: false
});
}
} catch (e) {
// handle the error by logging it and sending back an failure
console.log('Unexpected error occurred in the skill handler!', e);
if(typeof callback === 'undefined') {
context.fail("Unexpected error");
} else {
callback("Unexpected error");
}
}
};
and this is my handler GetMachineState.js
function sendResponse(context, callback, responseOptions) {
if(typeof callback === 'undefined') {
context.succeed(buildResponse(responseOptions));
} else {
callback(null, buildResponse(responseOptions));
}
}
function buildResponse(options) {
var alexaResponse = {
version: "1.0",
response: {
outputSpeech: {
"type": "SSML",
"ssml": `<speak><prosody rate="slow">${options.output}</prosody></speak>`
},
shouldEndSession: options.endSession
}
};
if (options.repromptText) {
alexaResponse.response.reprompt = {
outputSpeech: {
"type": "SSML",
"ssml": `<speak><prosody rate="slow">${options.reprompt}</prosody></speak>`
}
};
}
return alexaResponse;
}
exports.handler = (event, context, callback) => {
try {
var request = event.request;
if (request.type === "LaunchRequest") {
sendResponse(context, callback, {
output: "welcome to my skill. what do you want to find?",
endSession: false
});
}
else if (request.type === "IntentRequest") {
let options = {};
if (request.intent.name === "GetMachineStateIntent") {
// this is where we will wire up the dynamo call
// for now, just send a simple response and end the session
sendResponse(context, callback, {
output: "cinema not implemented yet!",
endSession: true
});
} else if (request.intent.name === "AMAZON.StopIntent" || request.intent.name === "AMAZON.CancelIntent") {
sendResponse(context, callback, {
output: "ok. good bye!",
endSession: true
});
}
else if (request.intent.name === "AMAZON.HelpIntent") {
sendResponse(context, callback, {
output: "you can ask me about incidents that have happened",
reprompt: "what can I help you with?",
endSession: false
});
}
else {
sendResponse(context, callback, {
output: "I don't know that one! Good bye!",
endSession: true
});
}
}
else if (request.type === "SessionEndedRequest") {
sendResponse(context, callback, ""); // no response needed
}
else {
// an unexpected request type received.. just say I don't know..
sendResponse(context, callback, {
output: "I don't know that one! Good bye!",
endSession: true
});
}
} catch (e) {
// handle the error by logging it and sending back an failure
console.log('Unexpected error occurred in the skill handler!', e);
if(typeof callback === 'undefined') {
context.fail("Unexpected error");
} else {
callback("Unexpected error");
}
}
};
Its impossible to know for sure if this is the problem or not because you haven't shared the code from the index.js file. The error message you get is telling you that the problem occurs at line 70 in your index.js file so you should look there, and figure out what the problem is.
However, based on the fact that you also posted this as a comment on another question, I'm going to venture to guess that the issue you've run into is that you used the code snippet I provided in the answer to that question and the error is from dereferencing the request.type
You have to make sure the request variable is set to the actual request from the event, like so: var request = event.request where event is provided from exports.handler = (event, context, callback) => {
For example:
exports.handler = (event, context, callback) => {
var request = event.request;
if (request.type === "IntentRequest"
// make suret the name of the intent matches the one in your interaction model
&& request.intent.name == "GetMachineStateIntent") {
var dateSlot = request.intent.slots.Date != null ?
request.intent.slots.Date.value : "unknown date";
var timeSlot = request.intent.slots.Time != null ?
request.intent.slots.Time.value : "unknown time";
// respond with speech saying back what the skill thinks the user requested
sendResponse(context, callback, {
output: "You wanted the machine state at "
+ timeSlot + " on " + dateSlot,
endSession: true
});
} else {
// TODO: handle other types of requests..
sendResponse(context, callback, {
output: "I don't know how to handle this request yet!"
endSession: true
});
}
};
function sendResponse(context, callback, responseOptions) {
if(typeof callback === 'undefined') {
context.succeed(buildResponse(responseOptions));
} else {
callback(null, buildResponse(responseOptions));
}
}
function buildResponse(options) {
var alexaResponse = {
version: "1.0",
response: {
outputSpeech: {
"type": "SSML",
"ssml": `<speak><prosody rate="slow">${options.output}</prosody></speak>`
},
shouldEndSession: options.endSession
}
};
if (options.repromptText) {
alexaResponse.response.reprompt = {
outputSpeech: {
"type": "SSML",
"ssml": `<speak><prosody rate="slow">${options.reprompt}</prosody></speak>`
}
};
}
return alexaResponse;
}
I'm applying Angular 4 in my project and I am having trouble with Custom Validation using HTTP request.
I also tried this: How to implement Custom Async Validator in Angular2.
But it doesn't work in my project.
Here's what I've done so far:
Validation biding:
userName: ['', [Validators.required, Validators.minLength(3), this.validateUserName.bind(this)]]
Value changes event:
let userNameControl = this.individualForm.get('userName');
userNameControl.valueChanges.subscribe(value => {
this.setErrorMessagesForUserNameControl(userNameControl)
}
);
Validation function:
validateUserName(control: FormControl): Promise<any> {
let promise = new Promise<any>(
(resolve, reject) => {
if (this.oldUserNameForCheck != undefined) {
this._individualUpdateService.isUserNameExisting(this.oldUserNameForCheck, control.value).subscribe(
(res) => {
if (res.isUserNameExisting) {
console.log("existing");
resolve({'existing': true});
} else {
console.log("NOT existing");
resolve(null);
}
},
(error) => {
console.log(error);
}
);
} else {
resolve(null);
}
}
);
return promise;
}
Error Messages
I just try to validate the username by sending it to the back-end.
Here's the logs
As you can see, there's a message for "required", "minlength". Those work fine. But the message for Custom validation is not quite clear.
Async validator should be in the next parameter
userName: ['',
[ Validators.required, Validators.minLength(3) ],
[ this.validateUserName.bind(this) ]
]
Also its better to have a factory method with required dependencies that would create validator, instead of using 'bind(this)'
userNameValidator(originalUsername, individualUpdateService) {
return (control: FormControl): Promise<any> => {
return new Promise<any>((resolve, reject) => {
if (originalUsername != undefined) {
individualUpdateService.isUserNameExisting(originalUsername, control.value).subscribe(
(res) => {
if (res.isUserNameExisting) {
console.log("existing");
resolve({ 'existing': true });
} else {
console.log("NOT existing");
resolve(null);
}
},
(error) => {
console.log(error);
}
);
} else {
resolve(null);
}
})
}
}
userName: ['',
[ Validators.required, Validators.minLength(3) ],
[ this.userNameValidator(this.oldUserNameForCheck, this._individualUpdateService) ]
]
outputFields: {
token: {
type: GraphQLString,
resolve: (token) => token
}
},
outputfields never gets called, not sure whether i am doing in a right way or not, doesn't the resolve function gets called while returning data from mutateAndGetPayload method.
mutateAndGetPayload: (credentials) => {
console.log('credentials', credentials);
userprof.findOne({email: credentials.email}).exec(function(err, r) {
if(!r) {
return new Error('no user')
} else if(r) {
if(r.password != credentials.password) {
return new Error('password error');
} else {
var token = jwt.getToken(r);
console.log(token);
return {token};
}
}
});
}
I think that you need to return something from the mutateAndGetPayload method. That could be a promise. Try to return the userprof.findOne.
Solution
token: {
type: GraphQLString,
resolve: ({token}) => token
}
},
mutateAndGetPayload: (credentials) => {
return UserProf.findOne({ email: credentials.email }).then((r) => {
if (!r) {
return new Error('no user');
} else if (r) {
if (r.password != credentials.password) {
return new Error('password error');
} else {
return { token: jwt.getToken(r) };
}
}
});
}