0

I'm retrieving a form (via XMPP, XEP-0004), creating an interactive form dialog for it, then submitting the form when the dialog is closed.

The code (roughly approximated for simplicity):

function form(name, callback) {
  server.getForm(name, function(response) {
    callback(response.formFields, function (data) {
      server.submitForm(name, data);
    });
  });
}

function main() {
  form('example', function(fields, callback) {
    var dialog = ui.formDialog(fields);
    dialog.addButton('submit', function(data) {
      callback(data);
    });
    dialog.show();
  });
}

Note how the caller and callee exchange callbacks - in one direction, for the fields retrieved from the server; in the other, for the user-submitted data.

I've recently discovered JS Promises and I'd like to know if they could replace the callbacks more elegantly.

I got as far as:

function form(name) {
  return new Promise((resolve, reject) => {
    server.getForm(
      name,
      (response) => { resolve(response.formFields) },
      reject
    );
  });
}

function main() {
  form('example').then((fields) => {
    var dialog = ui.formDialog(fields);
    dialog.addButton('submit', /* ... */);
  });
}

But now I'm stuck, because I have no way to pass the submit button's event back to the form() call.

I can't simply create a Promise for the dialog either, because I'd have to create that promise first in order to pass it to form(), but I need the promise returned by form() to be resolved before I can create the dialog. There's a sort of bootstrap problem.

Is there some way to use promises here, or should I stick with passing callbacks back and forth?

Christoph Burschka
  • 4,467
  • 3
  • 16
  • 31

3 Answers3

2

You could break the problem into three parts:

  1. form - Retrieve the form - function that takes name and returns Promise resolved with the fields.
  2. askUser - Gather input - function that takes fields, displays form and returns a Promise with the submitted data.
  3. submit - Submit form - function that takes data and returns a Promise of network result.

You can than compose the three together:

form('example')
  .then(askUser)
  .then(submit)
  .catch(errorHandler)

Which would return a Promise eventually resolved with the submit operation.

1

You can still pass callback along the response. Simply pass array of response and callback to the resolve. And within .then callback you might destruct the array. In short you can write something like: response => resolve([response.formFields, **your_callback**]) and .then(([fields, callback]) .... But in my opinion the code is smelly and you should decompose from function into two independent functions to avoid passing callback back and forth

ZigGreen
  • 168
  • 1
  • 9
1

form wraps its behaviour around a callback, you cannot really avoid that callback without splitting form in two functions. However, it's totally fine - it's known as the disposer pattern.

You'd use it like this:

function form(name, callback) {
  return server.getForm(name).then(function(response) {
    return callback(response.formFields);
  }).then(function (data) {
    return server.submitForm(name, data);
  }).catch(function(e) {
    console.error(e); // or display it in the form, or whatever
  });
}

function main() {
  return form('example', function(fields) {
    return new Promise(function(resolve, reject) {
      var dialog = ui.formDialog(fields);
      dialog.addButton('submit', resolve);
      dialog.show();
    });
  });
}
Community
  • 1
  • 1
Bergi
  • 630,263
  • 148
  • 957
  • 1,375