I'm learning dart and have created a simple contact form - based on the forms tutorial on the dart site, using mailer to send a mail.
So far so good - the client post the message, the server picks up the post data, creates an envelope and sends the mail... but I want to display a comforting message to the person filling out the form, or warn them if it were not sent.
The problem is that the http response is sent before the future that sends the email is completed -
Chris Storm talks about using completers to solve this problem, I think - here: testing error conditions with dart and again in a chapter in Dart for Hipsters, but i am finding it hard to work out how to apply it here.
Here is the complete server code - the comments in the sendMail function show the problem.
import 'dart:io';
import 'dart:convert';
import 'package:mailer/mailer.dart';
final HOST = '127.0.0.1'; // eg: localhost
final PORT = 4040; // a port, must match the client program
void main() {
HttpServer.bind(HOST, PORT).then(gotMessage, onError: printError);
}
void gotMessage(_server) {
_server.listen((HttpRequest request) {
switch (request.method) {
case 'POST':
handlePost(request);
break;
case 'OPTIONS':
handleOptions(request);
break;
default: defaultHandler(request);
}
},
onError: printError); // .listen failed
print('Listening for GET and POST on http://$HOST:$PORT');
}
/**
* Handle POST requests
*
*/
void handlePost(HttpRequest req) {
HttpResponse res = req.response;
print('${req.method}: ${req.uri.path}');
addCorsHeaders(res);
req.listen((List<int> buffer) {
// Create a new string from the characters in the buffer and convert this into a map
Map postData = JSON.decode(new String.fromCharCodes(buffer));
res.write(sendMail(postData));
res.close();
},
onError: printError);
}
/**
* Add Cross-site headers to enable accessing this server from pages
* not served by this server
*
* See: http://www.html5rocks.com/en/tutorials/cors/
* and http://enable-cors.org/server.html
*/
void addCorsHeaders(HttpResponse res) {
res.headers.add('Access-Control-Allow-Origin', '*, ');
res.headers.add('Access-Control-Allow-Methods', 'POST, OPTIONS');
res.headers.add('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept');
}
void handleOptions(HttpRequest req) {
HttpResponse res = req.response;
addCorsHeaders(res);
print('${req.method}: ${req.uri.path}');
res.statusCode = HttpStatus.NO_CONTENT;
res.close();
}
void defaultHandler(HttpRequest req) {
HttpResponse res = req.response;
addCorsHeaders(res);
res.statusCode = HttpStatus.NOT_FOUND;
res.write('Not found: ${req.method}, ${req.uri.path}');
res.close();
}
void printError(error) => print(error);
sendMail(postData) {
String sentMsg = 'I am waiting for a result to report'; //initialised value
var options = new GmailSmtpOptions()
..username = '[email protected]' //shove real values in here to test
..password = 'my_password';
var transport = new SmtpTransport(options);
// Create the envelope to send.
var envelope = new Envelope()
..fromName = postData['name']
..from = postData['fromEmail']
..recipients = ['[email protected]']
..subject = 'Message from contact form'
..text = postData['contactMessage'];
transport.send(envelope)
.then((success) => sentMsg = 'Message sent, thank you.' ) //this will not be returned...
.catchError((e) => sentMsg = 'Message not sent; the reported error was: $e'); // nor will this
return sentMsg; // Sadly the initial value will be returned as the future (transport.send) will not have completed
}
If I get it right, you want to send a response based on the result on transport.send()
. If so you can just return it, like
return transport.send(envelope)
.then((success) => sentMsg = 'Message sent, thank you.' )
.catchError((e) => sentMsg = 'Message not sent; the reported error was: $e');
Or using Completer
var c = new Completer();
transport.send(envelope)
.then((success) => sentMsg = 'Message sent, thank you.' )
.then(c.complete)
.catchError((e) => sentMsg = 'Message not sent; the reported error was: $e');
return c.future;
And since you are returning a Future
, you have to change how you send your response to client, from
res.write(sendMail(postData));
res.close();
to
sendMail(postData)
.then((result) {
res.write(result);
res.close();
});