Search code examples
javascriptfirefox-addonfirefox-addon-sdkpromise

Chaining multiple asynchronous functions


Introduction

I have a question coming from this one:

Loop calling an asynchronous function

Once I got a loop that calls an asynchronous function using a promises-like pattern (jsdeferred library in an add-on developed with SDK).

var sess = ...;
Deferred.define(this);      
function asyncFunction (session) {
    Deferred.next(function() {
        var d = new Deferred();
        (new Request({
              url: form.php,
              content: "sess=" + session,
              onComplete: function (response) {
                  d.call(response.json);
              }
        })).get();
        return d;
    }).next(function(resp) {
        if (resp.id) {
            asyncFunction(session);
            console.log(resp.msg);              
        }           
    });
}

asyncFunction(sess);

I want to chain a new asynchrnous function into this request, for example, opening a browser's tab: I don't want to continue the execution until is ready.

Problem

So I get to this:

var sess = ...;
Deferred.define(this);      
function asyncFunction (session) {
    Deferred.next(function() {
        var d = new Deferred();
        (new Request({
              url: form.php,
              content: "sess=" + session,
              onComplete: function (response) {
                  d.call(response.json);
              }
        })).get();
        return d;
    })
    .next(function(resp) {
        var d = new Deferred();
        // Load tab
        tabs.activeTab.url = 'http://foo.com';
        tabs.activeTab.on('ready', function() {
            console.log(resp.id);
            d.call(resp.id); 
        });
        return d;
    })
    .next(function(resp) {
        if (resp.id) {
            asyncFunction(session);
            console.log(resp.msg);              
        }           
    });
}

asyncFunction(sess);

And the result that I get is that, for each iteration of the loop, it runs all the previous requests again! So I got the following log in my console:

id1
msg1
id1
msg1
id2
msg2
id1
msg1
id2
msg2
id3
msg3
...

Obviously what I expected was:

id1
msg1
id2
msg2
id3
msg3

Question

It's crystal clear that I don't understand the promises pattern. I've read a lot in two APIs:

The first is the one I've been using until now. The second one gives an error when I try to require the library:

const { defer } = require('api-utils/promise'); (+)

So, my question is if you know any good tutorial of promises or some good example about chaining multiple asynchronous functions. Or if you know how to do it, how would you do it.

Thank you

EDIT: The error that gives the line (+)

error: An exception occurred.
Traceback (most recent call last):
  File "resource://ares-at-iiia-dot-csic/api-utils/lib/promise.js", line 16, in 
    }.call(this, function(require, exports) {
  File "resource://ares-at-iiia-dot-csic/api-utils/lib/promise.js", line 6, in return     function promised
    if (typeof(define) === 'function') { // RequireJS
  File "function promisedConcat(promises, unknown) {return promises.then(function         (values) {return resolve(unknown).then(function (valu
e) {return values.concat(value);});});", line NaN, in function promisedConcat
  File "function execute(args) {return call.apply(call, args);", line NaN, in function     execute
  File "exports.reject = reject;var promised = function () {var call =     Function.call;var concat = Array.prototype.concat", line NaN, in
 exports.reject = reject;var promised = function 
  File "function reject(reason, prototype) {var deferred =         defer(prototype);deferred.reject(reason);return deferred.promise;", line NaN
, in function reject
  File "exports.resolve = resolve", line NaN, in exports.resolve = resolve
  File "function resolve(value, prototype) {var deferred =     defer(prototype);deferred.resolve(value);return deferred.promise;", line NaN
, in function resolve
  File "exports.defer = defer", line NaN, in exports.defer = defer
  File "if (pending) {pending.push([resolved, rejected]);} else {result.then(resolved,     rejected);}return deferred.promise;}}});var defe
rred = {promise: promise, resolve: function resolve(value) {if (pending) {result =     isPromise(value) ? value : resolution(value);while (
pending.length) {result.then.apply(result, pending.shift());}pending = null;}},     reject", line NaN, in if 
  File "function rejected(reason) {deferred.resolve(reject(reason));", line NaN, in     function rejected
  File "function resolved(value) {deferred.resolve(resolve(value));", line NaN, in     function resolved
  File "function defer(prototype) {var pending = [], result;prototype = prototype ||     prototype === null ? prototype : Object.prototype;
var promise = Object.create(prototype, {then: {value: function then(resolve, reject)     {var deferred = defer(prototype);resolve = resolve
 ? attempt(resolve) : resolution;reject = reject ? attempt(reject) ", line NaN, in     function defer
  File "function isPromise(value) {return value && typeof value.then === "function";",     line NaN, in function isPromise
  File "function attempt(f) {return function effort(options) {try {return f(options);}     catch (error) {return rejection(error);}};", lin
e NaN, in function attempt
  File "function rejection(reason) {return {then", line NaN, in function rejection
  File "function resolution(value) {return {then", line NaN, in function resolution
  File "((function (require, exports) {"use strict"", line NaN, in 
  File "resource://ares-at-iiia-dot-csic/api-utils/lib/globals!.js", line 75, in getter
    value: define.bind(this)
TypeError: can't redefine non-configurable property 'define'

Solution

  • Hm, looks a bit like a scoping issue. In your last function, there should be no variable resp available - I'm not sure what it really logs.

    And yes, because you call asyncFunction once again in the end, as long as there is an id in the response, you might get an endless loop.

    However, lets try to use the add-on sdk's promises (and no, I've never used them before - but it says it was based on CommonJS Promises/A):

    const { defer } = require('api-utils/promise');
    // or whatever you need in your code to use them - I'm no addOn-developer
    
    var sess = ...;
    Deferred.define(this);      
    function asyncFunction (session) {
    /* returns a promise itself */
    
        var d = defer();
        (new Request({
            url: form.php,
            content: "sess=" + session,
            onComplete: function (response) {
                d.resolve(response.json);
            }
        })).get();
        return d.promise.then(function(resp) {
            var d = defer();
            // Load tab
            tabs.activeTab.url = 'http://foo.com';
            tabs.activeTab.on('ready', function() {
                d.resolve(resp);
            });
            return d.promise;
        }).then(function(resp) {
            console.log("request #"+resp.id+"sucessful: "+resp.msg);  
            if (resp.id) {
                asyncFunction(session); // call recursively
            }           
        });
    }
    asyncFunction(sess);