События и слушатели
Когда к статье добавляют комментарий, нужно сохранить его, отправить письмо автору и, может быть, обновить счётчик. Если всё это писать в контроллере, он разрастается. Событие — сообщение «что-то произошло», а слушатель — код, который на него реагирует. Контроллер только сообщает о событии и не знает, кто и что сделает в ответ.
Класс события
node ace make:event CommentCreatedПоявится файл app/events/comment_created.ts. Событие хранит данные о том, что произошло:
// app/events/comment_created.ts
import { BaseEvent } from '@adonisjs/core/events'
import Comment from '#models/comment'
export default class CommentCreated extends BaseEvent {
constructor(public comment: Comment) {
super()
}
}Отправка события
// app/controllers/comments_controller.ts
import CommentCreated from '#events/comment_created'
const comment = await post.related('comments').create({ body: payload.body })
CommentCreated.dispatch(comment)Статический метод dispatch создаёт событие с переданными аргументами и сообщает о нём всем слушателям. Контроллер сразу продолжает работу.
Слушатель
node ace make:listener NotifyPostAuthor// app/listeners/notify_post_author.ts
import mail from '@adonisjs/mail/services/main'
import CommentCreated from '#events/comment_created'
export default class NotifyPostAuthor {
async handle(event: CommentCreated) {
const comment = event.comment
await comment.load('post', (query) => query.preload('author'))
await mail.sendLater((message) => {
message
.to(comment.post.author.email)
.subject('Новый комментарий')
.html(`<p>К статье «${comment.post.title}» оставили комментарий.</p>`)
})
}
}Метод handle получает объект события. Здесь подразумевается, что у статьи есть связь author с моделью User.
Регистрация слушателя
Связь событий и слушателей описывают в файле start/events.ts. Если его нет, создайте командой node ace make:preload events:
// start/events.ts
import emitter from '@adonisjs/core/services/emitter'
import CommentCreated from '#events/comment_created'
const NotifyPostAuthor = () => import('#listeners/notify_post_author')
emitter.on(CommentCreated, [NotifyPostAuthor, 'handle'])Слушатель подключается так же лениво, как контроллер. Теперь после каждого нового комментария автор статьи получит письмо. Чтобы добавить ещё одно действие, достаточно нового слушателя — контроллер менять не нужно.
Простая форма
Для небольших реакций можно передать функцию вместо класса:
// start/events.ts
emitter.on(CommentCreated, (event) => {
console.log(`Новый комментарий к статье ${event.comment.postId}`)
})В терминале сервера появится строка о каждом новом комментарии.
Частая ошибка
Ошибка в слушателе не возвращается в контроллер: событие уже отправлено, и посетитель увидит успешный ответ, даже если письмо не ушло. Поэтому в слушатели выносят только второстепенные действия. То, без чего операция считается неудачной, например запись в базу, оставляйте в контроллере.