@vbarbarosh/node-helpers 3.60.0 → 3.61.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "author": "Vladimir Barbarosh <vladimir.barbarosh@gmail.com>",
3
3
  "license": "MIT",
4
4
  "name": "@vbarbarosh/node-helpers",
5
- "version": "3.60.0",
5
+ "version": "3.61.0",
6
6
  "description": "A set of helpers for JavaScript/Node.js",
7
7
  "files": [
8
8
  "src"
@@ -1,3 +1,10 @@
1
+ /**
2
+ * Usage:
3
+ * array_index(items, v => v.name)
4
+ *
5
+ * Seems, there is a native way to do it:
6
+ * Object.fromEntries(items.map(v => [v.name, v]));
7
+ */
1
8
  function array_index(array, fn)
2
9
  {
3
10
  const out = {};
@@ -1,5 +1,6 @@
1
1
  /**
2
- * Create fcmp from an array of props. For desc order a prop should be prefixed with minus sign (e.g. -price).
2
+ * Create fcmp from an array of props. For desc order a prop should be prefixed
3
+ * with minus sign (e.g. -price).
3
4
  */
4
5
  function fcmp_from_spec(props)
5
6
  {
@@ -0,0 +1,75 @@
1
+ /**
2
+ * Creates a filtering function based on the following spec:
3
+ * 1. Substrings are separated by `/`.
4
+ * 2. Each substring may have special characters:
5
+ * - `^`: Substring must appear at the beginning.
6
+ * - `$`: Substring must appear at the end.
7
+ * - `!`: Negates the condition (substring must not appear).
8
+ */
9
+ function filter1_from_spec(spec)
10
+ {
11
+ let a, b, c, d;
12
+ const parts = parse_spec(spec);
13
+ switch (parts.length) {
14
+ case 0:
15
+ // No filters, always true
16
+ return () => true;
17
+ case 1:
18
+ return parts[0];
19
+ case 2:
20
+ [a, b] = parts;
21
+ return s => a(s) && b(s);
22
+ case 3:
23
+ [a, b, c] = parts;
24
+ return s => a(s) && b(s) && c(s);
25
+ case 4:
26
+ [a, b, c, d] = parts;
27
+ return s => a(s) && b(s) && c(s) && d(s);
28
+ default:
29
+ return s => parts.every(fn => fn(s));
30
+ }
31
+ }
32
+
33
+ function parse_spec(spec)
34
+ {
35
+ return spec.split('/').filter(v => v).map(parse_expr);
36
+ }
37
+
38
+ // convert expr into an array of objects {substr, starts, ends}
39
+ function parse_expr(expr)
40
+ {
41
+ let substr;
42
+ let starts = false; // ^
43
+ let ends = false; // $
44
+ substr = expr.replaceAll('^', '');
45
+ starts = (substr.length !== expr.length);
46
+ expr = substr;
47
+ substr = expr.replaceAll('$', '');
48
+ ends = (substr.length !== expr.length);
49
+ expr = substr;
50
+ substr = expr.replaceAll('!', '');
51
+ if ((expr.length - substr.length) % 2) { // not
52
+ if (starts && ends) {
53
+ return s => !s.startsWith(substr) && !s.endsWith(substr);
54
+ }
55
+ if (starts) {
56
+ return s => !s.startsWith(substr);
57
+ }
58
+ if (ends) {
59
+ return s => !s.endsWith(substr);
60
+ }
61
+ return s => !s.includes(substr);
62
+ }
63
+ if (starts && ends) {
64
+ return s => s.startsWith(substr) && s.endsWith(substr);
65
+ }
66
+ if (starts) {
67
+ return s => s.startsWith(substr);
68
+ }
69
+ if (ends) {
70
+ return s => s.endsWith(substr);
71
+ }
72
+ return s => s.includes(substr);
73
+ }
74
+
75
+ module.exports = filter1_from_spec;
@@ -0,0 +1,31 @@
1
+ const assert = require('assert');
2
+ const filter1_from_spec = require('./filter1_from_spec');
3
+
4
+ const scenarios = [
5
+ // Format: [spec, input, expected, description]
6
+ ['substr', 'this is a substr example', true, 'Single substring match'],
7
+ ['^start', 'start of the string', true, 'Starts with substring'],
8
+ ['end$', 'this is the end', true, 'Ends with substring'],
9
+ ['!foo', 'this string does not contain it', true, 'Does not contain substring'],
10
+ ['^start/end$', 'start and end', true, 'Starts and ends with substrings'],
11
+ ['substr1/substr2', 'contains substr1 and substr2', true, 'Two matches'],
12
+ ['substr1/substr2/substr3', 'contains substr1, substr2, and substr3', true, 'Three matches'],
13
+ ['substr1/substr2/substr3/substr4', 'contains substr1, substr2, substr3, and substr4', true, 'Four matches'],
14
+ ['substr1/substr2/substr3/substr4/substr5', 'contains substr1, substr2, substr3, substr4, substr5', true, 'Five matches'],
15
+ ['!not1/!not2', 'this string avoids nota and notb', true, 'Does not contain multiple substrings'],
16
+ ['^start1/^start2', 'start1 followed by start2', false, 'Conflicting start substrings'],
17
+ ['end1$/end2$', 'ends with end1 or end2', false, 'Conflicting end substrings'],
18
+ ['^start1/!substr1', 'start1 without substr', true, 'Starts with start1 and have no substr1'],
19
+ ['substr/!not', 'contains substr but no ...', true, 'Combination of positive and negative substrings'],
20
+ ['!missing', 'this string does not mention it', true, 'Negative substring absent'],
21
+ ['missing', 'this string does not mention it', false, 'Substring missing']
22
+ ];
23
+
24
+ describe('filter1_from_spec', function () {
25
+ scenarios.forEach(function ([spec, input, expected, description]) {
26
+ it(`${description || spec}`, function () {
27
+ const filter = filter1_from_spec(spec);
28
+ assert.strictEqual(filter(input), expected, description);
29
+ });
30
+ });
31
+ });
@@ -18,3 +18,11 @@ function format_bytes(bytes)
18
18
  }
19
19
 
20
20
  module.exports = format_bytes;
21
+
22
+ // https://wiki.ubuntu.com/UnitsPolicy
23
+ // > Applications must use SI standard for base-10 units:
24
+ // >
25
+ // > 1 kB = 1,000 bytes (Note: small k)
26
+ // > 1 MB = 1,000 kB = 1,000,000 bytes
27
+ // > 1 GB = 1,000 MB = 1,000,000 kB = 1,000,000,000 bytes
28
+ // > 1 TB = 1,000 GB = 1,000,000 MB = 1,000,000,000 kB = 1,000,000,000,000 bytes
@@ -0,0 +1,15 @@
1
+ const sanitize_dash_name = require('./sanitize_dash_name');
2
+
3
+ function sanitize_var_name(var_name)
4
+ {
5
+ const out = sanitize_dash_name(var_name).replace(/[^a-z0-9]+/g, '_');
6
+ if (out.length === 0) {
7
+ return '_';
8
+ }
9
+ if (out[0].match(/[0-9]/)) {
10
+ return '_' + out;
11
+ }
12
+ return out;
13
+ }
14
+
15
+ module.exports = sanitize_var_name;
@@ -0,0 +1,22 @@
1
+ const assert = require('assert');
2
+ const sanitize_var_name = require('./sanitize_var_name');
3
+
4
+ const items = [
5
+ ['', '_'],
6
+ [' ', '_'],
7
+ [' ', '_'],
8
+ ['Product ID', 'product_id'],
9
+ ['Product-ID', 'product_id'],
10
+ ['Image URL', 'image_url'],
11
+ ['Ópera Prima', 'opera_prima'],
12
+ ['1000-stories', '_1000_stories'],
13
+ ['A, C, D', 'a_c_d'],
14
+ ];
15
+
16
+ describe('sanitize_var_name', function () {
17
+ items.forEach(function ([input, expected],) {
18
+ it(`${input === '' ? '☒' : input} → ${expected}`, function () {
19
+ assert.deepStrictEqual(sanitize_var_name(input), expected);
20
+ });
21
+ });
22
+ });
@@ -11,7 +11,7 @@ async function shell_curl_progress(args, {options, user_friendly_status})
11
11
  // > you need to redirect the response output to a file, using
12
12
  // > shell redirect (>), -o, --output or similar.
13
13
 
14
- const tmp = ['--progress-meter'];
14
+ const tmp = ['--no-silent', '--progress-meter'];
15
15
  if (!args.includes('-o')) {
16
16
  tmp.push('-o', '/dev/null');
17
17
  }
@@ -0,0 +1,3 @@
1
+ % Total % Received % Xferd Average Speed Time Time Time Current
2
+ Dload Upload Total Spent Left Speed
3
+
4
  0 0 0 0 0 0 0 0 --:--:-- --:--:-- --:--:-- 0
1
5
  2 390k 2 10934 0 0 10234 0 0:00:39 0:00:01 0:00:38 10237
2
6
  5 390k 5 21877 0 0 10569 0 0:00:37 0:00:02 0:00:35 10573
3
7
  8 390k 8 32125 0 0 10241 0 0:00:39 0:00:03 0:00:36 10243
4
8
  10 390k 10 43059 0 0 10406 0 0:00:38 0:00:04 0:00:34 10408
5
9
  14 390k 14 56853 0 0 10922 0 0:00:36 0:00:05 0:00:31 10922
6
10
  17 390k 17 69113 0 0 10548 0 0:00:37 0:00:06 0:00:31 10610
7
11
  20 390k 20 81782 0 0 10552 0 0:00:37 0:00:07 0:00:30 10546
8
12
  20 390k 20 81782 0 0 10239 0 0:00:39 0:00:07 0:00:32 10238
9
13
  23 390k 23 94460 0 0 10509 0 0:00:38 0:00:08 0:00:30 10598
10
14
  26 390k 26 103k 0 0 10378 0 0:00:38 0:00:10 0:00:28 9814
11
15
  29 390k 29 115k 0 0 10455 0 0:00:38 0:00:11 0:00:27 10325
12
16
  32 390k 32 127k 0 0 10352 0 0:00:38 0:00:12 0:00:26 10032
13
17
  35 390k 35 139k 0 0 10418 0 0:00:38 0:00:13 0:00:25 10667
14
18
  35 390k 35 139k 0 0 10241 0 0:00:39 0:00:13 0:00:26 9754
15
19
  39 390k 39 152k 0 0 10402 0 0:00:38 0:00:14 0:00:24 10456
16
20
  41 390k 41 163k 0 0 10327 0 0:00:38 0:00:16 0:00:22 10028
17
21
  45 390k 45 175k 0 0 10381 0 0:00:38 0:00:17 0:00:21 10456
18
22
  48 390k 48 188k 0 0 10371 0 0:00:38 0:00:18 0:00:20 10238
19
23
  51 390k 51 199k 0 0 10311 0 0:00:38 0:00:19 0:00:19 10479
20
24
  51 390k 51 199k 0 0 10240 0 0:00:39 0:00:19 0:00:20 9752
21
25
  54 390k 54 211k 0 0 10356 0 0:00:38 0:00:20 0:00:18 10456
22
26
  57 390k 57 223k 0 0 10304 0 0:00:38 0:00:22 0:00:16 10030
23
27
  61 390k 61 239k 0 0 10503 0 0:00:38 0:00:23 0:00:15 11020
24
28
  65 390k 65 255k 0 0 10487 0 0:00:38 0:00:24 0:00:14 11171
25
29
  65 390k 65 255k 0 0 10241 0 0:00:39 0:00:25 0:00:14 10242
26
30
  69 390k 69 271k 0 0 10473 0 0:00:38 0:00:26 0:00:12 10912
27
31
  69 390k 69 271k 0 0 10240 0 0:00:39 0:00:27 0:00:12 9952
28
32
  73 390k 73 287k 0 0 10458 0 0:00:38 0:00:28 0:00:10 10234
29
33
  77 390k 77 303k 0 0 10448 0 0:00:38 0:00:29 0:00:09 10245
30
34
  77 390k 77 303k 0 0 10241 0 0:00:39 0:00:30 0:00:09 10241
31
35
  81 390k 81 319k 0 0 10436 0 0:00:38 0:00:31 0:00:07 10231
32
36
  85 390k 85 335k 0 0 10427 0 0:00:38 0:00:32 0:00:06 11303
33
37
  85 390k 85 335k 0 0 10240 0 0:00:39 0:00:33 0:00:06 9110
34
38
  90 390k 90 351k 0 0 10418 0 0:00:38 0:00:34 0:00:04 10231
35
39
  90 390k 90 351k 0 0 10241 0 0:00:39 0:00:35 0:00:04 10240
36
40
  94 390k 94 367k 0 0 10411 0 0:00:38 0:00:36 0:00:02 10248
37
41
  98 390k 98 383k 0 0 10403 0 0:00:38 0:00:37 0:00:01 10236
38
42
  98 390k 98 383k 0 0 10240 0 0:00:39 0:00:38 0:00:01 10238
@@ -0,0 +1,42 @@
1
+ [
2
+ {"perc":"0","done":"0","total":"0","perc2":"0","recv":"0","perc3":"0","transferred":"0","speed_down":"0","speed_up":"0","time_total":"~","duration":"~","eta":"~","speed":"0"},
3
+ {"perc":"2","done":"10934","total":"390k","perc2":"2","recv":"10934","perc3":"0","transferred":"0","speed_down":"10234","speed_up":"0","time_total":"0:00:39","duration":"0:00:01","eta":"0:00:38","speed":"10237"},
4
+ {"perc":"5","done":"21877","total":"390k","perc2":"5","recv":"21877","perc3":"0","transferred":"0","speed_down":"10569","speed_up":"0","time_total":"0:00:37","duration":"0:00:02","eta":"0:00:35","speed":"10573"},
5
+ {"perc":"8","done":"32125","total":"390k","perc2":"8","recv":"32125","perc3":"0","transferred":"0","speed_down":"10241","speed_up":"0","time_total":"0:00:39","duration":"0:00:03","eta":"0:00:36","speed":"10243"},
6
+ {"perc":"10","done":"43059","total":"390k","perc2":"10","recv":"43059","perc3":"0","transferred":"0","speed_down":"10406","speed_up":"0","time_total":"0:00:38","duration":"0:00:04","eta":"0:00:34","speed":"10408"},
7
+ {"perc":"14","done":"56853","total":"390k","perc2":"14","recv":"56853","perc3":"0","transferred":"0","speed_down":"10922","speed_up":"0","time_total":"0:00:36","duration":"0:00:05","eta":"0:00:31","speed":"10922"},
8
+ {"perc":"17","done":"69113","total":"390k","perc2":"17","recv":"69113","perc3":"0","transferred":"0","speed_down":"10548","speed_up":"0","time_total":"0:00:37","duration":"0:00:06","eta":"0:00:31","speed":"10610"},
9
+ {"perc":"20","done":"81782","total":"390k","perc2":"20","recv":"81782","perc3":"0","transferred":"0","speed_down":"10552","speed_up":"0","time_total":"0:00:37","duration":"0:00:07","eta":"0:00:30","speed":"10546"},
10
+ {"perc":"20","done":"81782","total":"390k","perc2":"20","recv":"81782","perc3":"0","transferred":"0","speed_down":"10239","speed_up":"0","time_total":"0:00:39","duration":"0:00:07","eta":"0:00:32","speed":"10238"},
11
+ {"perc":"23","done":"94460","total":"390k","perc2":"23","recv":"94460","perc3":"0","transferred":"0","speed_down":"10509","speed_up":"0","time_total":"0:00:38","duration":"0:00:08","eta":"0:00:30","speed":"10598"},
12
+ {"perc":"26","done":"103k","total":"390k","perc2":"26","recv":"103k","perc3":"0","transferred":"0","speed_down":"10378","speed_up":"0","time_total":"0:00:38","duration":"0:00:10","eta":"0:00:28","speed":"9814"},
13
+ {"perc":"29","done":"115k","total":"390k","perc2":"29","recv":"115k","perc3":"0","transferred":"0","speed_down":"10455","speed_up":"0","time_total":"0:00:38","duration":"0:00:11","eta":"0:00:27","speed":"10325"},
14
+ {"perc":"32","done":"127k","total":"390k","perc2":"32","recv":"127k","perc3":"0","transferred":"0","speed_down":"10352","speed_up":"0","time_total":"0:00:38","duration":"0:00:12","eta":"0:00:26","speed":"10032"},
15
+ {"perc":"35","done":"139k","total":"390k","perc2":"35","recv":"139k","perc3":"0","transferred":"0","speed_down":"10418","speed_up":"0","time_total":"0:00:38","duration":"0:00:13","eta":"0:00:25","speed":"10667"},
16
+ {"perc":"35","done":"139k","total":"390k","perc2":"35","recv":"139k","perc3":"0","transferred":"0","speed_down":"10241","speed_up":"0","time_total":"0:00:39","duration":"0:00:13","eta":"0:00:26","speed":"9754"},
17
+ {"perc":"39","done":"152k","total":"390k","perc2":"39","recv":"152k","perc3":"0","transferred":"0","speed_down":"10402","speed_up":"0","time_total":"0:00:38","duration":"0:00:14","eta":"0:00:24","speed":"10456"},
18
+ {"perc":"41","done":"163k","total":"390k","perc2":"41","recv":"163k","perc3":"0","transferred":"0","speed_down":"10327","speed_up":"0","time_total":"0:00:38","duration":"0:00:16","eta":"0:00:22","speed":"10028"},
19
+ {"perc":"45","done":"175k","total":"390k","perc2":"45","recv":"175k","perc3":"0","transferred":"0","speed_down":"10381","speed_up":"0","time_total":"0:00:38","duration":"0:00:17","eta":"0:00:21","speed":"10456"},
20
+ {"perc":"48","done":"188k","total":"390k","perc2":"48","recv":"188k","perc3":"0","transferred":"0","speed_down":"10371","speed_up":"0","time_total":"0:00:38","duration":"0:00:18","eta":"0:00:20","speed":"10238"},
21
+ {"perc":"51","done":"199k","total":"390k","perc2":"51","recv":"199k","perc3":"0","transferred":"0","speed_down":"10311","speed_up":"0","time_total":"0:00:38","duration":"0:00:19","eta":"0:00:19","speed":"10479"},
22
+ {"perc":"51","done":"199k","total":"390k","perc2":"51","recv":"199k","perc3":"0","transferred":"0","speed_down":"10240","speed_up":"0","time_total":"0:00:39","duration":"0:00:19","eta":"0:00:20","speed":"9752"},
23
+ {"perc":"54","done":"211k","total":"390k","perc2":"54","recv":"211k","perc3":"0","transferred":"0","speed_down":"10356","speed_up":"0","time_total":"0:00:38","duration":"0:00:20","eta":"0:00:18","speed":"10456"},
24
+ {"perc":"57","done":"223k","total":"390k","perc2":"57","recv":"223k","perc3":"0","transferred":"0","speed_down":"10304","speed_up":"0","time_total":"0:00:38","duration":"0:00:22","eta":"0:00:16","speed":"10030"},
25
+ {"perc":"61","done":"239k","total":"390k","perc2":"61","recv":"239k","perc3":"0","transferred":"0","speed_down":"10503","speed_up":"0","time_total":"0:00:38","duration":"0:00:23","eta":"0:00:15","speed":"11020"},
26
+ {"perc":"65","done":"255k","total":"390k","perc2":"65","recv":"255k","perc3":"0","transferred":"0","speed_down":"10487","speed_up":"0","time_total":"0:00:38","duration":"0:00:24","eta":"0:00:14","speed":"11171"},
27
+ {"perc":"65","done":"255k","total":"390k","perc2":"65","recv":"255k","perc3":"0","transferred":"0","speed_down":"10241","speed_up":"0","time_total":"0:00:39","duration":"0:00:25","eta":"0:00:14","speed":"10242"},
28
+ {"perc":"69","done":"271k","total":"390k","perc2":"69","recv":"271k","perc3":"0","transferred":"0","speed_down":"10473","speed_up":"0","time_total":"0:00:38","duration":"0:00:26","eta":"0:00:12","speed":"10912"},
29
+ {"perc":"69","done":"271k","total":"390k","perc2":"69","recv":"271k","perc3":"0","transferred":"0","speed_down":"10240","speed_up":"0","time_total":"0:00:39","duration":"0:00:27","eta":"0:00:12","speed":"9952"},
30
+ {"perc":"73","done":"287k","total":"390k","perc2":"73","recv":"287k","perc3":"0","transferred":"0","speed_down":"10458","speed_up":"0","time_total":"0:00:38","duration":"0:00:28","eta":"0:00:10","speed":"10234"},
31
+ {"perc":"77","done":"303k","total":"390k","perc2":"77","recv":"303k","perc3":"0","transferred":"0","speed_down":"10448","speed_up":"0","time_total":"0:00:38","duration":"0:00:29","eta":"0:00:09","speed":"10245"},
32
+ {"perc":"77","done":"303k","total":"390k","perc2":"77","recv":"303k","perc3":"0","transferred":"0","speed_down":"10241","speed_up":"0","time_total":"0:00:39","duration":"0:00:30","eta":"0:00:09","speed":"10241"},
33
+ {"perc":"81","done":"319k","total":"390k","perc2":"81","recv":"319k","perc3":"0","transferred":"0","speed_down":"10436","speed_up":"0","time_total":"0:00:38","duration":"0:00:31","eta":"0:00:07","speed":"10231"},
34
+ {"perc":"85","done":"335k","total":"390k","perc2":"85","recv":"335k","perc3":"0","transferred":"0","speed_down":"10427","speed_up":"0","time_total":"0:00:38","duration":"0:00:32","eta":"0:00:06","speed":"11303"},
35
+ {"perc":"85","done":"335k","total":"390k","perc2":"85","recv":"335k","perc3":"0","transferred":"0","speed_down":"10240","speed_up":"0","time_total":"0:00:39","duration":"0:00:33","eta":"0:00:06","speed":"9110"},
36
+ {"perc":"90","done":"351k","total":"390k","perc2":"90","recv":"351k","perc3":"0","transferred":"0","speed_down":"10418","speed_up":"0","time_total":"0:00:38","duration":"0:00:34","eta":"0:00:04","speed":"10231"},
37
+ {"perc":"90","done":"351k","total":"390k","perc2":"90","recv":"351k","perc3":"0","transferred":"0","speed_down":"10241","speed_up":"0","time_total":"0:00:39","duration":"0:00:35","eta":"0:00:04","speed":"10240"},
38
+ {"perc":"94","done":"367k","total":"390k","perc2":"94","recv":"367k","perc3":"0","transferred":"0","speed_down":"10411","speed_up":"0","time_total":"0:00:38","duration":"0:00:36","eta":"0:00:02","speed":"10248"},
39
+ {"perc":"98","done":"383k","total":"390k","perc2":"98","recv":"383k","perc3":"0","transferred":"0","speed_down":"10403","speed_up":"0","time_total":"0:00:38","duration":"0:00:37","eta":"0:00:01","speed":"10236"},
40
+ {"perc":"98","done":"383k","total":"390k","perc2":"98","recv":"383k","perc3":"0","transferred":"0","speed_down":"10240","speed_up":"0","time_total":"0:00:39","duration":"0:00:38","eta":"0:00:01","speed":"10238"},
41
+ {"perc":"100","done":"390k","total":"390k","perc2":"100","recv":"390k","perc3":"0","transferred":"0","speed_down":"10422","speed_up":"0","time_total":"0:00:38","duration":"0:00:38","eta":"~","speed":"10457"}
42
+ ]
@@ -0,0 +1,17 @@
1
+ const assert = require('assert');
2
+ const fs = require('fs');
3
+ const fs_path_resolve = require('./fs_path_resolve');
4
+ const fs_read_json = require('./fs_read_json');
5
+ const stream = require('stream');
6
+ const stream_curl_progress = require('./stream_curl_progress');
7
+
8
+ describe('stream_curl_progress', function () {
9
+ it('should handle basic input', async function () {
10
+ const expected = await fs_read_json(fs_path_resolve(__dirname, 'stream_curl_progress.d/basic.json'));
11
+ const actual = await stream.compose(
12
+ fs.createReadStream(fs_path_resolve(__dirname, 'stream_curl_progress.d/basic')),
13
+ stream_curl_progress(),
14
+ ).toArray();
15
+ assert.deepStrictEqual(actual, expected);
16
+ });
17
+ });
package/src/waitcb.js CHANGED
@@ -4,7 +4,7 @@ const Promise = require('bluebird');
4
4
  * Wait for a Node-like function to finish (which will call `callback`
5
5
  * with 2 arguments: `error` and `value`).
6
6
  *
7
- * await waitcb(cb => fs.promises.writeFile('a', 'hello\n', cb));
7
+ * await waitcb(cb => fs.writeFile('a', 'hello\n', cb));
8
8
  */
9
9
  function waitcb(fn)
10
10
  {