Статьи

Мой новый любимый трюк JavaScript

Использование возвратов и обратных вызовов в одной и той же функции.

Звучит как сумасшедший разговор, я знаю, но выслушай меня, у меня есть веская причина Думаю.

Допустим, вы хотите создать несколько TCP- серверов в приложении node.js. Приходится слушать на нескольких портах или что угодно. Использование заводской функции — лучший способ избежать повторения кода, верно?

Вы получите что-то вроде этого:

var server = function (port, callback) {
    var server = net.createServer(function (connection) {});
 
    server.on("connection", function (socket) {
        socket.on("data", // data stuff);
 
        socket.on("error", function () {
            console.log("error?", arguments);
            socket.destroy();
        });
        socket.on("close", // cleanup stuff);
    });
 
    server.on("listening", callback);
    server.listen(port);
};

Просто. Вызовите функцию, назначьте ей порт, получите уведомление, когда сервер будет готов к прослушиванию. Никогда не сможет снова прикоснуться к серверу.

Подожди, это не хорошо. Что делать, если вы хотите сослаться на сервер позже? Чтобы закрыть это, например.

Вот где использование return вместе с обратными вызовами приходит.

Просто добавление return serverвнизу этой функции позволяет нам сделать что-то вроде этого:

var make_servers = function (callback) {
    this.servers = {};
 
    async.each([5000, 5001, 5002],
               _.bind(function (port, callback) {
                    this.servers[port] = server(port, callback);
               }, this),
               function (err) {
                    callback();
               });
 
    return this;
}

Я украдкой добавил библиотеки async и underscore, потому что они облегчают жизнь.

make_serversбудет генерировать три сервера прослушивает порты 5000в 5002. Библиотека async помогла нам гарантировать, что основной обратный вызов вызывается только после того, как все серверы готовы к прослушиванию, а использование _.bindпозволяет привязать генерацию серверов к текущей области.

Когда эта функция выполнена, она возвращает свою область видимости, которая теперь включает ссылки на все серверы, и сообщит вашему коду продолжать работу, когда все серверы будут готовы.

Вы бы использовали что-то вроде этого:

var stuff = make_servers(function () { // all servers listening });
 
// stuff.servers can access all servers

Если вы привязываете все обратные вызовы внутри serverк текущей области, вы даже можете отслеживать соединения. Вы бы в конечном итоге что-то вроде этого:

var server = function (port) {
    var server = net.createServer(function (connection) {});
 
    server.on("connection", _.bind(function (socket) {
        socket.id = shortid.generate();
        this.connections[port][socket.id] = socket;
 
        socket.on("data", // data stuff);
 
        socket.on("error", function () {
            console.log("error?", arguments);
            socket.destroy();
        });
        socket.on("close", _.bind(function () {
            delete this.connections[port][socket.id];
        }, this));
    }, this));
 
    return function (callback) {
        server.on("listening", callback);
        server.listen(port);        
        return server;
    };
};

Не так много изменилось. Было связано больше вещей, thisи возвращаемое значение стало функцией, потому что я чувствую, что частичное применение делает этот код чище.

Вы бы назвать serverзавод , как это сейчас: this.servers[port] = server(port)(callback). Основным преимуществом этого подхода является то, что мы можем генерировать серверы в цикле и активировать их позднее.

Мы по существу отделили генерацию сервера и запуск сервера. Может пригодиться.

stuffОбъект от лица теперь будет иметь , stuff.connectionsа также, какие ссылки в настоящее время все открытые подключения к каждому порту. Ухоженная!

Еще одна уловка, которую я использовал в этих примерах, — это мощный динамический обзор Javascript . Получение полуслучайных функций, запускаемых в одной и той же области, может действительно очистить ваш код.

За счет того, что иногда он слишком умен. Используйте на свой страх и риск.

Как вы думаете, есть ли более чистый способ реализовать что-то подобное?