Skip to content

Commit 2c01ce3

Browse files
authored
Address Race Conditions and Better Error Handling to fix Issue #256 (#316)
* Issue #256 Better Error Handling There were a couple of anti patterns being used, and improper use of promises that lead to race conditions: 1. Using `process.exit()` directly is usually not advisable as it prevents things from shutting down nicely. It also means if someone is trying to use this as a library, which I think may be the case for issue #256, you will exit from their code as well (i.e., error handling becomes impossible). 2. Functions that caught errors were printing them directly to STDERR rather than rethrowing them or throwing a new error. Again, if someone is trying to use it as a library as in the issues, this will make it impossible for them to do error handling. 3. There were some places where errors were being caught and printed to STDERR, but not causing the the process to exit with a non-zero exit code. It's always advisable to exit with a non-zero exit code when there is a failure. 4. There were some places where promises were not being waited for, leading to race conditions. * Fix audit issues
1 parent f483430 commit 2c01ce3

File tree

3 files changed

+84
-94
lines changed

3 files changed

+84
-94
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,4 @@ npm-debug.log
44
.idea
55
coverage/
66
.nyc_output
7+
.vscode/

bin/tldr

Lines changed: 19 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -84,29 +84,36 @@ if (program.theme) {
8484

8585
const tldr = new Tldr(cfg);
8686

87+
let p = null;
8788
if (program.list) {
88-
tldr.list(program.singleColumn);
89+
p = tldr.list(program.singleColumn);
8990
} else if (program.listAll) {
90-
tldr.listAll(program.singleColumn);
91+
p = tldr.listAll(program.singleColumn);
9192
} else if (program.random) {
92-
tldr.random(program);
93+
p = tldr.random(program);
9394
} else if (program.randomExample) {
94-
tldr.randomExample();
95+
p = tldr.randomExample();
9596
} else if (program.clearCache) {
96-
tldr.clearCache();
97+
p = tldr.clearCache();
9798
} else if (program.update) {
98-
tldr.updateCache()
99+
p = tldr.updateCache()
99100
.then(() => {
100-
tldr.updateIndex();
101+
return tldr.updateIndex();
101102
});
102103
} else if (program.render) {
103-
tldr.render(program.render);
104+
p = tldr.render(program.render);
104105
} else if (program.search) {
105106
program.args.unshift(program.search);
106-
tldr.search(program.args);
107+
p = tldr.search(program.args);
107108
} else if (program.args.length >= 1) {
108-
tldr.get(program.args, program);
109-
} else {
109+
p = tldr.get(program.args, program);
110+
}
111+
112+
if (p === null) {
110113
program.outputHelp();
111-
process.exit(1);
114+
process.exitCode = 1;
112115
}
116+
p.catch((err) => {
117+
console.error(err);
118+
process.exitCode = 1;
119+
});

lib/tldr.js

Lines changed: 64 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ const messages = require('./messages');
1111
const parser = require('./parser');
1212
const render = require('./render');
1313
const index = require('./index');
14-
const exit = process.exit;
14+
1515

1616
class Tldr {
1717
constructor(config) {
@@ -23,34 +23,33 @@ class Tldr {
2323

2424
list(singleColumn) {
2525
let os = platform.getPreferredPlatformFolder(this.config);
26-
index.commandsFor(os)
26+
return index.commandsFor(os)
2727
.then((commands) => {
28-
this.printPages(commands, singleColumn);
28+
return this.printPages(commands, singleColumn);
2929
});
3030
}
3131

3232
listAll(singleColumn) {
33-
index.commands()
33+
return index.commands()
3434
.then((commands) => {
35-
this.printPages(commands, singleColumn);
35+
return this.printPages(commands, singleColumn);
3636
});
3737
}
3838

3939
get(commands, options) {
40-
this.printBestPage(commands.join('-'), options);
40+
return this.printBestPage(commands.join('-'), options);
4141
}
4242

4343
random(options) {
4444
let os = platform.getPreferredPlatformFolder(this.config);
45-
index.commandsFor(os)
45+
return index.commandsFor(os)
4646
.then((pages) => {
4747
if (pages.length === 0) {
48-
console.error(messages.emptyCache());
49-
exit(1);
48+
throw new Error(messages.emptyCache());
5049
}
5150
let page = sample(pages);
5251
console.log('PAGE', page);
53-
this.printBestPage(page, options);
52+
return this.printBestPage(page, options);
5453
})
5554
.catch((err) => {
5655
console.error(err);
@@ -59,138 +58,107 @@ class Tldr {
5958

6059
randomExample() {
6160
let os = platform.getPreferredPlatformFolder(this.config);
62-
index.commandsFor(os)
61+
return index.commandsFor(os)
6362
.then((pages) => {
6463
if (pages.length === 0) {
65-
console.error(messages.emptyCache());
66-
exit(1);
64+
throw new Error(messages.emptyCache());
6765
}
6866
let page = sample(pages);
6967
console.log('PAGE', page);
70-
this.printBestPage(page, {randomExample: true});
68+
return this.printBestPage(page, {randomExample: true});
7169
})
7270
.catch((err) => {
7371
console.error(err);
7472
});
7573
}
7674

7775
render(file) {
78-
fs.readFile(file, 'utf8')
76+
return fs.readFile(file, 'utf8')
7977
.then((content) => {
8078
// Getting the shortindex first to populate the shortindex var
8179
return index.getShortIndex().then(() => {
8280
this.renderContent(content);
8381
});
84-
})
85-
.catch((err) => {
86-
console.error(err);
87-
exit(1);
8882
});
8983
}
9084

9185
clearCache() {
92-
this.cache.clear().then(() => {
86+
return this.cache.clear().then(() => {
9387
console.log('Done');
9488
});
9589
}
9690

9791
updateCache() {
98-
const spinner = ora();
99-
spinner.start('Updating...');
100-
return this.cache.update()
101-
.then(() => {
102-
spinner.succeed();
103-
})
104-
.catch((err) => {
105-
console.error(err);
106-
exit(1);
107-
});
92+
return spinningPromise('Updating...', () => {
93+
return this.cache.update();
94+
});
10895
}
10996

11097
updateIndex() {
111-
const spinner = ora();
112-
spinner.start('Creating index...');
113-
search.createIndex()
114-
.then(() => {
115-
spinner.succeed();
116-
})
117-
.catch((err) => {
118-
console.error(err);
119-
});
98+
return spinningPromise('Creating index...', () => {
99+
return search.createIndex();
100+
});
120101
}
121102

122103
search(keywords) {
123-
search.getResults(keywords.join(' '))
104+
return search.getResults(keywords.join(' '))
124105
.then((results) => {
125106
// TODO: make search into a class also.
126107
search.printResults(results, this.config);
127-
})
128-
.catch((err) => {
129-
console.error(err);
130108
});
131109
}
132110

133111
printPages(pages, singleColumn) {
134112
if (pages.length === 0) {
135-
console.error(messages.emptyCache());
136-
exit(1);
113+
throw new Error(messages.emptyCache());
137114
}
138-
this.checkStale();
139-
let endOfLine = require('os').EOL;
140-
let delimiter = singleColumn ? endOfLine : ', ';
141-
console.log('\n' + pages.join(delimiter));
115+
return this.checkStale()
116+
.then(() => {
117+
let endOfLine = require('os').EOL;
118+
let delimiter = singleColumn ? endOfLine : ', ';
119+
console.log('\n' + pages.join(delimiter));
120+
});
142121
}
143122

144123
printBestPage(command, options={}) {
145-
const spinner = ora();
146-
147124
// Trying to get the page from cache first
148-
this.cache.getPage(command)
125+
return this.cache.getPage(command)
149126
.then((content) => {
150127
// If found in first try, render it
151-
if (content) {
152-
this.checkStale();
153-
this.renderContent(content, options);
154-
return exit();
128+
if (!content) {
129+
// If not found, try to update
130+
return spinningPromise('Page not found. Updating cache...', () => {
131+
return this.cache.update();
132+
})
133+
.then(() => {
134+
return spinningPromise('Creating index...', () => {
135+
return search.createIndex();
136+
});
137+
})
138+
.then(() => {
139+
// And then, try to check in cache again
140+
return this.cache.getPage(command);
141+
});
155142
}
156-
// If not found, try to update
157-
spinner.start('Page not found. Updating cache...');
158-
return this.cache.update();
159-
})
160-
.then(() => {
161-
spinner.succeed();
162-
spinner.start('Creating index...');
163-
return search.createIndex();
164-
})
165-
.then(() => {
166-
spinner.succeed();
167-
// And then, try to check in cache again
168-
return this.cache.getPage(command);
143+
return content;
169144
})
170145
.then((content) => {
171146
if (!content) {
172-
console.error(messages.notFound());
173-
return exit(1);
147+
throw new Error(messages.notFound());
174148
}
175-
this.checkStale();
176-
this.renderContent(content, options);
177-
})
178-
.catch((err) => {
179-
console.error(err);
180-
exit(1);
149+
return this.checkStale()
150+
.then(() => {
151+
this.renderContent(content, options);
152+
});
181153
});
182154
}
183155

184156
checkStale() {
185-
this.cache.lastUpdated()
157+
return this.cache.lastUpdated()
186158
.then((stats) => {
187159
if (stats.mtime < Date.now() - ms('30d')) {
188160
console.warn('Cache is out of date. You should run "tldr --update"');
189161
}
190-
})
191-
.catch((err) => {
192-
console.error(err);
193-
exit(1);
194162
});
195163
}
196164

@@ -209,4 +177,18 @@ class Tldr {
209177
}
210178
}
211179

180+
function spinningPromise(text, factory) {
181+
const spinner = ora();
182+
spinner.start(text);
183+
return factory()
184+
.then((val) => {
185+
spinner.succeed();
186+
return val;
187+
})
188+
.catch((err) => {
189+
spinner.fail();
190+
throw err;
191+
});
192+
}
193+
212194
module.exports = Tldr;

0 commit comments

Comments
 (0)