Рубрики
Без рубрики

Демистификация асинхронного программирования Часть 2: Node.js Eventemitter

Как программист Node.js, как вы не можете быть знакомы с поведением асинхронного программирования? Прочитайте этот всеобъемлющий пост для важнейших идей в связи с циклом событий Node.js и шаблон события.

Автор оригинала: Simen Li.

Eventemitter.

Node.js славится своей асинхронной и приводом от событий. Я уверен, что вы чувствуете себя довольно хорошо о себе после прохождения небольшого анализа петель событий в нашем предыдущем посте. Теперь мы будем говорить о другой важной теме: Emitter Emitter. Позвольте мне сначала дать вам сводную информацию:

Node.js дал вам эмиттеров событий, чтобы позволить вам создавать инструменты для шаблона события в пространстве пользователя. Это не имеет ничего общего с петлями событий!

Какие?!?!

Вы можете чувствовать себя смущенным или, возможно, даже немного расстроен. Возможно, вы даже удивляетесь, знаете ли я вообще ody.js! Ну, держите лошадей и успокойся. Вы увидите, как раскрывается история.

Eventemitters синхронно

Совещание, в природе, Синхронный. К сожалению, ни одна из книг или статей, которые я прочитал, ясно указывал на это. Некоторые из них прыгают на ложные выводы, пока некоторые из них намекают на это. Одна книга даже считает, что Eventemitter является абстракцией контура событий (что совершенно неверно. Просто не спрашивай меня, какую книгу это было)!

Когда я был новичком на Node.js, я тоже верил. Пока я не создал Eventemitter и а Таймер в стиле Node.js в Луа. Вот когда я понял, что все не то, что я вообще представлял. И потому что я хотел написать его в стиле Node.js, я оказался «копированием», как это было сделано в Node.js (ну, уважая уважением к великим разработчикам XD).

Следующее также использует исходный код Node.js v4.5.0 LTS в качестве примера. Реализация Eventemitter в/lib/events.js составляет менее 450 строк. Существует два очень важных метода, которые являются частью режима события. Наша реализация почти все вращается вокруг методов .emit (событие, ...) и .on (слушатель) Отказ .on () Позволяет зарегистрировать слушатель событий, пока .emit () Позвольте вам излучать события. После того, как события выполняются обратные вызовы, зарегистрированные для прослушивания события. Я уверен, что разработчики JavaScript знакомы с этим режимом (ну не просто знакомы, это сгорело в США).

Конструктор Eventemitter выглядит так. Его структура действительно такая простая. Внутри, это просто защищенный элемент, this._event = {} Отказ В этой коробке тип события будет действовать в качестве ключа, и зарегистрированный слушатель будет действовать в качестве значения. Если событие имеет несколько слушателей, то значение – это массив, который хранит обработчики в порядке регистрации.

function EventEmitter() {
  EventEmitter.init.call(this);
}

EventEmitter.init = function() {
  // ... 
  if (!this._events || this._events === Object.getPrototypeOf(this)._events) {
    this._events = {};      // this object is used to manage the registered listeners
    this._eventsCount = 0;
  }

  this._maxListeners = this._maxListeners || undefined;
};

Давайте посмотрим на .on () первый. Так как это псевдоним для AddListener мы посмотрим на AddListener Метод:

EventEmitter.prototype.on = EventEmitter.prototype.addListener;
EventEmitter.prototype.addListener = function addListener(type, listener) {
  var events;
  var existing;
  // ... 
  events = this._events;

  // ... 
    existing = events[type];

  // If event type does not exist, then input type as the key and listener as the value
  if (!existing) {
    existing = events[type] = listener;
    ++this._eventsCount;
  } else {
    // If event already exisits and its value is function, then store as an array
    // if it's aready an array with two or more listeners, then push the new listener in
    if (typeof existing === 'function') {
      // Adding the second element, need to change to array.
      existing = events[type] = [existing, listener];
    } else {
      // If we've already got an array, just append.
      existing.push(listener);
    }
    // ... 
  }

  return this;
};

Разве это не так просто? Далее давайте посмотрим на .emit () :

EventEmitter.prototype.emit = function emit(type) {
  var er, handler, len, args, i, events, domain;
  // ... 
  events = this._events;
  // ... 

  handler = events[type];  // find the handler

  // if no listener for that type exisits, directly return
  if (!handler)
    return false;

  // ... 
  // the following cases are just different ways to call based on the number of arguments for the sake of node performance 
  // let's take emitOne and take a look
  switch (len) {
    // fast cases
    case 1:
      emitNone(handler, isFn, this);
      break;
    case 2:
      emitOne(handler, isFn, this, arguments[1]);
      // ... 
  }

  // ... 
  return true;
};

Мы объясним это, используя .emitone () Как типичный пример:

function emitOne(handler, isFn, self, arg1) {
  // if the handler is a function, then run it directly
  if (isFn)
    handler.call(self, arg1);

  // if not, then it's an array of functions
  else {
    var len = handler.length;
    var listeners = arrayClone(handler, len);

    // run the handlers in the array one by one in order
    // note: This is synchronous code
    for (var i = 0; i < len; ++i)
      listeners[i].call(self, arg1);
  }
}

Это говорит нам, что Каждый Emit будет вызывать синхронный код Отказ Обратные вызовы запускаются один за другим, пока все они не закончится. Разве это не звучит ужасно, как очередь обратного вызова, объясненная ранее в этом посте? Это именно то, что это такое! Но как работает EventEmitters, не имеет ничего общего с контуром событий Node.js – вы можете прочитать все события. Джей, но вы не найдете асинхронный код.

Если вы когда-либо использовали флюсовую архитектуру реагирования, его Диспетчер Использует ту же архитектуру для реализации полезной нагрузки и трансляции (см. Регистрация () и Отправка () методы. Вы видите сходство на на () и emit () кроме с большим количеством контроля статуса?).

Будьте очень осторожны при использовании Eventemitter

В Node.js, кроме оказания помощи с помощью управления рабочим потоком, мы в основном используем Eventemitter, чтобы уведомить, когда что-то произошло (или завершено), особенно когда некоторые асинхронные задачи были завершены или произошли (например, считывание файла, подключение к соединению, разъем закрыт , так далее.). Например:

fooEmitter.on('data', function (data) {
  console.log(data);
});

fs.readFile('/path/to/file', (err, data) => {
  if (!err)
    fooEmitter.emit('data', data);
});

Поскольку мы используем его, как вышеизложенное, мы создаем иллюзию, что «Использование Eventemitter – пишет асинхронный код».

Когда-либо видел собаку, преследую свой хвост? Давайте напишем один!

Мы излучаем 'Event2' Событие в обработчике Event11, затем выделяют 'Event3' Событие в обработчике Event22, затем, наконец, выделяют 'Event1' событие в обработчике Event3.

var EventEmitter = require("events");

var crazy = new EventEmitter();

crazy.on('event1', function () {
    console.log('event1 fired!');
    crazy.emit('event2');
});

crazy.on('event2', function () {
    console.log('event2 fired!');
    crazy.emit('event3');

});

crazy.on('event3', function () {
    console.log('event3 fired!');
    crazy.emit('event1');
});

crazy.emit('event1');

Идите вперед и беги! Вы получите исключение, что в основном говорит Стек вызовов взорвался Отказ Собака повернулась до смерти от головокружения. Почему? Потому что все обратные вызовы выполняются в синхронном порядке! Это просто рекурсивный звонок себе в бесконечность и за его пределами! Не думай, что это произойдет? Никогда не говори никогда!

Бессмертная собака

Что делать, если мы используем setimmediate () Чтобы начать его и отправить его в петлю событий? (Это как асинхронно, как это становится правильным?). Вы все равно получите один и тот же результат! То, что вы сделали, отправьте его в цикл событий, чтобы начать, но когда происходит событие, Весь цепь событий с синхронной , который будет заблокировать петлю события. Рекурсивный вызов в обратном вызове продолжается до тех пор, пока система не зависает с переполнением стека. По той же причине, даже если мы не создали события в качестве замкнутого цикла и имели каждый обработчик событий в качестве длительной задачи, что все равно будет заблокировать контур событий в течение длительного периода времени.

Теперь, что если вместо этого мы используем setimmediate () отправить каждый Эмит () В предыдущем коде в контуре события? Вы получите Бессмертную собаку, которая никогда не умирает:

var EventEmitter = require('events');

var crazy = new EventEmitter();

crazy.on('event1', function () {
    console.log('event1 fired!');
    setImmediate(function () {
        crazy.emit('event2');
    });
});

crazy.on('event2', function () {
    console.log('event2 fired!');
    setImmediate(function () {
        crazy.emit('event3');
    });

});

crazy.on('event3', function () {
    console.log('event3 fired!');
    setImmediate(function () {
        crazy.emit('event1');
    });
});

crazy.emit('event1');

Идите вперед и беги! Теперь у вас есть поистине асинхронная программа! Вы будете счастливы, потому что система больше не зависает!

Как насчет процесса .NextTick?

Теперь, когда вы достаточно знакомы с Process.NextTick , что если мы поменялись Все setimmediate () с Process.NextTick ? Как ты думаешь, что произойдет? (Не пытайтесь этого дома!)

// ... 
crazy.on('event1', function () {
    console.log('event1 fired!');
    // swap all setImmediate with process.nextTick
    process.nextTick(function () {
        crazy.emit('event2');
    });
});

// ... 
crazy.emit('event1');

Это застряло! И если вы ждете достаточно долго, около 30 секунд, это в конечном итоге даст вам исключение «процесс памяти». Теперь проблема не является переполнением стекла, это GC не способна восстановить память. (Каждый обработчик имеет собственное закрытие для доступа к сумасшествию на внешнем слое. Эта стоимость выходит из кучи.) Хотя вы не можете быть на 100%, почему GC не может успешно получить память, вы можете догадаться, что программа застрял в некоторой фазе, потому что всегда есть еще один Process.NextTick обратный вызов должен быть обработан. (Итак, контур событий полностью заблокирован. Переполнение кучи просто бонус лол)

Итак, о счастливыйте, возвращаюсь к тому, что я сказал ранее:

Node.js дал вам эмиттеров событий, чтобы позволить вам создавать инструменты для шаблона события в пространстве пользователя. Это абсолютно не имеет ничего общего с петлями событий! Итак, как на земле мы пишем «асинхронный шаблон событий»? Ну, теперь вы знаете, что все, что вам нужно сделать, это использовать Eventemitter с функциями API, которые могут бросить задачи в контур событий! ( Settimeout () , SetInterval () , Async I/O apis, Setimmediate () и Process.nextTick (). с Process.nextTick () , просто будьте осторожны, чтобы избежать рекурсивных звонков и вызывающих длительные задачи в обратном вызове, и вы должны быть в порядке.)

Если вы все еще не убеждены, то почему бы не попробовать выполнить следующий код. Как вы думаете, код будет продолжать или остановиться немедленно?

var EventEmitter = require('events');
var server = new EventEmitter();

server.on('data', function () {
    console.log('Am I waiting for data incoming?');
});

Если вы можете ответить на вопрос, не запустив код, то вы Действительно понять, что я говорил. Ничто здесь не было добавлено к контуру события.

2016/9/23: комментатор упомянул, что если вы запустите Процесс. NextTick. Пример бессмертной собаки в узле 4.4.7 В Windows он не переполнен! Я нахожу это очень интересно!

Заключение

В этих двух постах мы полностью отделили концепции Node.js петлей событий и The Eventemitter и объяснили их независимо. Они не должны быть вместе, чтобы начать с, и Eventemitter определенно не является абстракцией контура событий. После того, как мы кристально ясно о двух концепциях, вам очень удобно, используя два вместе.

Я надеюсь, что этот пост может помочь другим разработчикам, таким как я, которые увлечены JavaScript и Node.js, чтобы узнать больше о асинхронном поведении Node.js и вдохновлять их, чтобы продолжать писать лучший асинхронный код. Каждый может использовать и изменить этот пост как учебный материал. Между тем, если кто-то заменяет ошибку, пожалуйста, скажите мне, чтобы мы могли сделать этот пост лучше и точнее!

Если вы думаете, что посты хорошо написаны, пожалуйста, порекомендуйте их своим друзьям. Я не знаю, полезно ли это для интерфейса или нет, поэтому я собирался публиковать это только на Node.js tw. Хотя, пожалуйста, не стесняйтесь репоста этому.

Этот пост был переведен на английский язык по команде контента кодаментатора. Здесь «Это оригинальный китайский пост от Симона Ли.