Nesting Asynchronous Promises in ActionScript

934 views Asked by At

I have a situation where I need to perform dependent asynchronous operations. For example, check the database for data, if there is data, perform a database write (insert/update), if not continue without doing anything. I have written myself a promise based database API using promise-as3. Any database operation returns a promise that is resolved with the data of a read query, or with the Result object(s) of a write query. I do the following to nest promises and create one point of resolution or rejection for the entire 'initialize' operation.

public function initializeTable():Promise 
{ 
    var dfd:Deferred = new Deferred();
    select("SELECT * FROM table").then(tableDefaults).then(resolveDeferred(dfd)).otherwise(errorHandler(dfd));
    return dfd.promise;
}

public function tableDefaults(data:Array):Promise 
{
    if(!data || !data.length) 
    {
        //defaultParams is an Object of table default fields/values.
        return insert("table", defaultParams);
    } else 
    {
        var resolved:Deferred = new Deferred();
        resolved.resolve(null);
        return resolved.promise;
    }
}

public function resolveDeferred(deferred:Deferred):Function 
{
    return function resolver(value:*=null):void 
    {
        deferred.resolve(value);
    }
}

public function rejectDeferred(deferred:Deferred):Function 
{
    return function rejector(reason:*=null):void 
    {
        deferred.reject(reason);
    }
}

My main questions:

  1. Are there any performance issues that will arise from this? Memory leaks etc.? I've read that function variables perform poorly, but I don't see another way to nest operations so logically.
  2. Would it be better to have say a global resolved instance that is created and resolved only once, but returned whenever we need an 'empty' promise?

EDIT: I'm removing question 3 (Is there a better way to do this??), as it seems to be leading to opinions on the nature of promises in asynchronous programming. I meant better in the scope of promises, not asynchronicity in general. Assume you have to use this promise based API for the sake of the question.

2

There are 2 answers

6
Andrey Popov On

I usually don't write those kind of opinion based answers, but here it's pretty important. Promises in AS3 = THE ROOTS OF ALL EVIL :) And I'll explain you why..

First, as BotMaster said - it's weakly typed. What this means is that you don't use AS3 properly. And the only reason this is possible is because of backwards compatibility. The true here is, that Adobe have spent thousands of times so that they can turn AS3 into strongly type OOP language. Don't stray away from that.

The second point is that Promises, at first place, are created so that poor developers can actually start doing some job in JavaScript. This is not a new design pattern or something. Actually, it has no real benefits if you know how to structure your code properly. The thing that Promises help the most, is avoiding the so called Wall of Hell. But there are other ways to fix this in a natural manner (the very very basic thing is not to write functions within functions, but on the same level, and simply check the passed result).

The most important here is the nature of Promises. Very few people know what they actually do behind the scenes. Because of the nature of JavaScript (and ECMA script at all), there is no real way to tell if a function completed properly or not. If you return false / null / undefined - they are all regular return values. The only way they could actually say "this operation failed" is by throwing an error. So every promisified method, can potentially throw an error. And each error must be handled, or otherwise your code can stop working properly. What this means, is that every single action inside Promise is within try-catch block! Every time you do absolutely basic stuff, you wrap it in try-catch. Even this block of yours:

else 
{
    var resolved:Deferred = new Deferred();
    resolved.resolve(null);
    return resolved.promise;
}

In a "regular" way, you would simply use else { return null }. But now, you create tons of objects, resolvers, rejectors, and finally - you try-catch this block.

I cannot stress more on this, but I think you are getting the point. Try-catch is extremely slow! I understand that this is not a big problem in such a simple case like the one I just mentioned, but imagine you are doing it more and on more heavy methods. You are just doing extremely slow operations, for what? Because you can write lame code and just enjoy it..

The last thing to say - there are plenty of ways to use asynchronous operations and make them work one after another. Just by googling as3 function queue I found a few. Not to say that the event-based system is so flexible, and there are even alternatives to it (using callbacks). You've got it all in your hands, and you turn to something that is created because lacking proper ways to do it otherwise.

So my sincere advise as a person worked with Flash for a decade, doing casino games in big teams, would be - don't ever try using promises in AS3. Good luck!

0
fgb On
var dfd:Deferred = new Deferred();
select("SELECT * FROM table").then(tableDefaults).then(resolveDeferred(dfd)).otherwise(errorHandler(dfd));
return dfd.promise;

This is the The Forgotten Promise antipattern. It can instead be written as:

return select("SELECT * FROM table").then(tableDefaults);

This removes the need for the resolveDeferred and rejectDeferred functions.

var resolved:Deferred = new Deferred();
resolved.resolve(null);
return resolved.promise;

I would either extract this to another function, or use Promise.when(null). A global instance wouldn't work because it would mean than the result handlers from one call can be called for a different one.