UNPKG

5.35 kBJavaScriptView Raw
1/**
2 * @copyright Copyright (c) 2019 Maxim Khorin <maksimovichu@gmail.com>
3 */
4'use strict';
5
6const Base = require('../base/Component');
7
8module.exports = class Task extends Base {
9
10 static getConstants () {
11 return {
12 EVENT_BEFORE_RUN: 'beforeRun',
13 EVENT_DONE: 'done',
14 EVENT_FAIL: 'fail',
15 DAY_PERIOD: 24 * 3600 * 1000
16 };
17 }
18
19 constructor (config) {
20 super({
21 active: true,
22 startup: false, // start immediately
23 startDate: null, // Date
24 startTime: null, // 00:00:00
25 period: 0, // repeat timeout (seconds or ISO_8601#Duration)
26 repeats: 0, // 0 - endless
27 stopOnFail: true,
28 ...config
29 });
30 if (this.startup) {
31 this.startDate = new Date;
32 }
33 this.period = DateHelper.parseDuration(this.period);
34 this._counter = 0;
35 }
36
37 init () {
38 if (this.active) {
39 this.start();
40 }
41 }
42
43 stop () {
44 this._nextDate = null;
45 this.cancelJob();
46 }
47
48 start () {
49 this.stop();
50 if (this.isRunning()) {
51 return this.fail('Skip task start. Job in progress');
52 }
53 this._counter = 0;
54 this._lastStartDate = null;
55 this._lastEndtDate = null;
56 this._lastError = null;
57 this.setNextDate(this.startDate || this.getPeriodTime());
58 return true;
59 }
60
61 isActive () {
62 return this.active;
63 }
64
65 isRunning () {
66 return !!this._job;
67 }
68
69 canRepeat () {
70 return this.repeats === 0 || this._counter < this.repeats;
71 }
72
73 getLastError () {
74 return this._lastError;
75 }
76
77 getLastStartDate () {
78 return this._lastStartDate;
79 }
80
81 getLastEndDate () {
82 return this._lastEndDate;
83 }
84
85 getNextDate () {
86 return this._nextDate;
87 }
88
89 setNextDate (date) {
90 if (!date) {
91 return this._nextDate = null;
92 }
93 if (DateHelper.isValid(date)) {
94 return this._nextDate = new Date(date);
95 }
96 this.log('error', `Invalid next date: ${date}`);
97 return this._nextDate = null;
98 }
99
100 getPeriodTime () {
101 if (this.canRepeat()) {
102 if (this.startTime) {
103 return this.formatStartTime();
104 }
105 if (this.period) {
106 return Date.now() + this.period;
107 }
108 }
109 }
110
111 formatStartTime () {
112 let date = `${moment().format('YYYY-MM-DD')} ${this.startTime}`;
113 if (!DateHelper.isValid(date)) {
114 return this.log('error', `Invalid start time: ${date}`);
115 }
116 date = (new Date(date)).getTime();
117 return date < Date.now() ? (date + this.DAY_PERIOD) : date;
118 }
119
120 refresh () {
121 if (this._nextDate && Date.now() >= this._nextDate) {
122 this.setNextDate(this.getPeriodTime());
123 if (this.active) {
124 return this.execute();
125 }
126 }
127 }
128
129 async execute (data) {
130 if (this.isRunning()) {
131 return this.fail('Job not started. Previous one in progress');
132 }
133 try {
134 this._job = this.createJob();
135 await this.beforeRun();
136 this.processInternal(data); // no await
137 } catch (err) {
138 this._job = null;
139 return this.fail(err);
140 }
141 }
142
143 createJob () {
144 return this.spawn(this.job, {task: this});
145 }
146
147 cancelJob () {
148 if (!this.isRunning()) {
149 return false;
150 }
151 try {
152 this._job.cancel();
153 } catch (err) {
154 return this.fail(err);
155 }
156 }
157
158 async processInternal (data) {
159 if (!this.isRunning()) {
160 return false;
161 }
162 try {
163 this.log('info', `Job started: ${this._job.constructor.name}`);
164 this._lastStartDate = new Date;
165 const result = await this._job.execute(data);
166 if (this._job.isCanceled()) {
167 await this.fail('Job canceled');
168 } else {
169 this._counter += 1;
170 this._lastEndDate = new Date;
171 await this.done(result);
172 }
173 } catch (err) {
174 try {
175 await this.fail(err);
176 } catch (err) {
177 this.log('error', 'Failed', err);
178 }
179 }
180 this._job = null;
181 }
182
183 beforeRun () {
184 return this.trigger(this.EVENT_BEFORE_RUN);
185 }
186
187 done (result) {
188 return this.trigger(this.EVENT_DONE, new Event({result}));
189 }
190
191 fail (error) {
192 if (this.stopOnFail) {
193 this._nextDate = null;
194 }
195 this._lastError = error;
196 return this.trigger(this.EVENT_FAIL, new Event({error}));
197 }
198
199 log (type, message, data) {
200 this.scheduler.log(type, `${this.constructor.name}: ${this.id}: ${message}`, data);
201 }
202};
203module.exports.init();
204
205const moment = require('moment');
206const DateHelper = require('../helper/DateHelper');
207const Event = require('../base/Event');
\No newline at end of file