Asynchroniczna pułapka

pułapka

(Piotrek Koszuliński; @reinmarpl; http://code42.pl) @ MeetJS

Geneza tematu

Dlaczego?

Gdzie ta pułapka?

caution bananas!

Złap errora – def czy call?

try {
    var fn = function () {
        throw new Error();
    };
}
catch (err) {
    console.log('def', err);
}
 
try {
    setTimeout(fn, 1);
}
catch (err) {
    console.log('call', err);
}

Ups!

baseball fail

dziurawy catch...

Ups!

baseball fail

...wywala nam serwer...

Ups!

baseball fail
process.on('uncaughtException', function (err) {
    console.log('ups', err);
});

Ups!

baseball fail

Node'owa konwencja: fs.writeFile('sth', function (err, result) {});

Incepcja

presidents as russian nested dolls

Incepcja

Synchroniczne API:

app.get('/post/:title', function (req, res) {
    var post = db.getPostByTitle(req.params.title);
    var comments = post.getComments();
    var tags = post.getTags();
 
    res.render({
        post: post,
        comments: comments,
        tags: tags
    });
});

Incepcja

Asynchroniczne API:

app.get('/post/:title', function (req, res) {
    db.getPostByTitle(req.params.title, function (err, post) {
        post.getComments(function (err, comments) {
            post.getTags(function (err, tags) {
                res.render({
                    post: post,
                    comments: comments,
                    tags: tags
                });
            })
        });
    });
});
presidents as russian nested dolls

Incepcja – wyjątki

Synchroniczne API:

app.get('/post/:title', function (req, res) {
    try {
        var post = db.getPostByTitle(req.params.title);
        var comments = post.getComments();
        var tags = post.getTags();
 
        res.render({
            post: post,
            comments: comments,
            tags: tags
        });
    }
    catch (err) {
        res.render500(err);
    }
});

Incepcja – wyjątki

Asynchroniczne API:

app.get('/post/:title', function (req, res) {
    db.getPostByTitle(req.params.title, function (err, post) {
        if (err) return res.render500(err);
        post.getComments(function (err, comments) {
            if (err) return res.render500(err);
            post.getTags(function (err, tags) {
                if (err) return res.render500(err);
                res.render({
                    post: post,
                    comments: comments,
                    tags: tags
                });
            })
        });
    });
});

Synchronizacja

Konkatenacja plików w katalogu:

  1. odczytanie listy plików w katalogu
  2. odczytanie zawartości każdego z plików
  3. synchronizacja callbacków!
  4. połączenie zawartości
  5. zapisanie pliku wynikowego

Synchronizacja

fs.readdir(__dirname, function (err, names) {
    var l = names.length, opened = 0, content = [];
    names.forEach(function (name, i) {
        fs.readFile(name, 'utf-8', function (err, c) {
            content[i] = c;
            if (++opened === l) write();
        });
    });
    var write = function () {
        fs.writeFile('lib.js', content.join("\n"), function (err) {
            console.log('done');
        });
    };
});

Synchronizacja

fs.readdir(__dirname, function (err, names) {
    var l = names.length, opened = 0, content = [];
    names.forEach(function (name, i) {
        fs.readFile(name, 'utf-8', function (err, c) { 
            content[i] = c;
            if (++opened === l) write();
        });
    });
    var write = function () { 
        fs.writeFile('lib.js', content.join("\n"), function (err) { 
            console.log('done');
        });
    };
});

Kto nas uratuje z pułapki?

Ktoś kto...

Bruce!

Async

Async

async.parallel([
    function(callback){
        setTimeout(function () {
            callback(null, 'one');
        }, 200);
    },
    function(callback){
        setTimeout(function () {
            callback(null, 'two');
        }, 100);
    },
],
function (err, results) {
    results; // -> ['two', 'one']
});

Async

app.get('/post/:title', function (req, res) {
    async.waterfall([
        db.getPostByTitle.bind(db, req.params.title),
        function (post, callback) {
            async.parallel({
                c: post.getComments.bind(post),
                t: post.getTags.bind(post)
            },
            function (err, results) {
                callback(err, post, results.c, results.t);
            });
        }
    ],
    function (err, post, comments, tags) {
        if (err) return res.render500(err);
        res.render({ ... });
    });
});

Deferred i promise'y

Załóżmy, że mamy asynchroniczną funkcję:

var oneOneSecondLater = function (callback) {
    setTimeout(function () {
        callback(1);
    }, 1000);
};
 
oneOneSecondLater(function (v) { console.log(v); });

Deferred i promise'y

Spróbujmy inaczej:

var maybeOneOneSecondLater = function () {
    var callback;
    setTimeout(function () {
        callback(1);
    }, 1000);
    return {
        then: function (_callback) {
            callback = _callback;
        }
    };
};
 
maybeOneOneSecondLater().then(function (v) {
    console.log(v);
});

całość rozmyślań u Krisa Kowala (https://github.com/kriskowal/q/blob/master/design/README.js)

Deferred i promise'y

Deferred i promise'y

var later = function () {
    var d = deferred();
    setTimeout(function () {
        d.resolve(1);
    }, 1000);
    return d.promise;
};
 
later().then(function (n) {
    console.log(n); // 1
});

Ale promise, to tak naprawdę then, więc prościej:

later()
(function (n) {
    console.log(n); // 1
});

Deferred i promise'y

Deferred i promise'y

Deferred i promise'y

Deferred i promise'y

Konkatenacja plików w katalogu:

all(
    // Read all filenames in given path
    a2p(fs.readdir, __dirname),
    // Read files content
    function (files) {
        return join(files.map(function (name) {
            return a2p(fs.readFile, name, 'utf-8');
        }));
    },
    // Concat into one string
    function (data) {
        return data.join("\n");
    },
    // Write to lib.js
    ba2p(fs.writeFile, __dirname + '/lib.js')
).end();

Deferred i promise'y

W skrócie:

all(
    a2p(fs.readdir, __dirname),
    invoke('map', function (name) {
        return a2p(fs.readFile, name, 'utf-8');
    }), join,
    invoke('join', "\n"),
    ba2p(fs.writeFile, __dirname + '/lib.js')
).end();

Deferred i promise'y

app.get('/post/:title', function (req, res) {
    a2p(db.getPostByTitle.bind(db), req.params.title)
    (function (post) {
        return all(
            post,
            a2p(post.getComments.bind(post)),
            a2p(post.getTags.bind(post))
        );
    })
    (function (args) {
        res.render({
            post: args[0],
            comments: args[1],
            tags: args[2]
        });
    })
    .end(function (err) {
        res.render500(err);
    });
});

Deferred i promise'y

Bądź gdybyśmy korzystali z deferred również w bazie:

app.get('/post/:title', function (req, res) {
    db.getPostByTitle(req.params.title)
    (function (post) {
        return all(
            post, post.getComments(), post.getTags()
        );
    })
    (function (args) {
        res.render({
            post: args[0],
            comments: args[1],
            tags: args[2]
        });
    })
    .end(function (err) {
        res.render500(err);
    });
});

Deferred i promise'y

Porównanie z synchronicznym API:

app.get('/post/:title', function (req, res) {
    try {
        var post = db.getPostByTitle(req.params.title);
        var comments = post.getComments();
        var tags = post.getTags();
 
        res.render({
            post: post,
            comments: comments,
            tags: tags
        });
    }
    catch (err) {
        res.render500(err);
    }
});

Async czy deferred czy ...?

uncle sam

Debugowanie

Rozterki na przyszłość

Pytania?

#

/