Skip to content

Commit 47f1095

Browse files
authored
Fix UnhandledPromiseRejectionWarning in copy (#1058)
1 parent 5e62bb7 commit 47f1095

File tree

2 files changed

+41
-19
lines changed

2 files changed

+41
-19
lines changed

lib/copy/copy.js

Lines changed: 12 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ const { mkdirs } = require('../mkdirs')
66
const { pathExists } = require('../path-exists')
77
const { utimesMillis } = require('../util/utimes')
88
const stat = require('../util/stat')
9+
const { asyncIteratorConcurrentProcess } = require('../util/async')
910

1011
async function copy (src, dest, opts = {}) {
1112
if (typeof opts === 'function') {
@@ -113,28 +114,20 @@ async function onDir (srcStat, destStat, src, dest, opts) {
113114
await fs.mkdir(dest)
114115
}
115116

116-
const promises = []
117-
118-
// loop through the files in the current directory to copy everything
119-
for await (const item of await fs.opendir(src)) {
117+
// iterate through the files in the current directory to copy everything
118+
await asyncIteratorConcurrentProcess(await fs.opendir(src), async (item) => {
120119
const srcItem = path.join(src, item.name)
121120
const destItem = path.join(dest, item.name)
122121

123-
promises.push(
124-
runFilter(srcItem, destItem, opts).then(include => {
125-
if (include) {
126-
// only copy the item if it matches the filter function
127-
return stat.checkPaths(srcItem, destItem, 'copy', opts).then(({ destStat }) => {
128-
// If the item is a copyable file, `getStatsAndPerformCopy` will copy it
129-
// If the item is a directory, `getStatsAndPerformCopy` will call `onDir` recursively
130-
return getStatsAndPerformCopy(destStat, srcItem, destItem, opts)
131-
})
132-
}
133-
})
134-
)
135-
}
136-
137-
await Promise.all(promises)
122+
const include = await runFilter(srcItem, destItem, opts)
123+
// only copy the item if it matches the filter function
124+
if (include) {
125+
const { destStat } = await stat.checkPaths(srcItem, destItem, 'copy', opts)
126+
// If the item is a copyable file, `getStatsAndPerformCopy` will copy it
127+
// If the item is a directory, `getStatsAndPerformCopy` will call `onDir` recursively
128+
await getStatsAndPerformCopy(destStat, srcItem, destItem, opts)
129+
}
130+
})
138131

139132
if (!destStat) {
140133
await fs.chmod(dest, srcStat.mode)

lib/util/async.js

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
'use strict'
2+
3+
// https://github.com/jprichardson/node-fs-extra/issues/1056
4+
// Performing parallel operations on each item of an async iterator is
5+
// surprisingly hard; you need to have handlers in place to avoid getting an
6+
// UnhandledPromiseRejectionWarning.
7+
// NOTE: This function does not presently handle return values, only errors
8+
async function asyncIteratorConcurrentProcess (iterator, fn) {
9+
const promises = []
10+
for await (const item of iterator) {
11+
promises.push(
12+
fn(item).then(
13+
() => null,
14+
(err) => err ?? new Error('unknown error')
15+
)
16+
)
17+
}
18+
await Promise.all(
19+
promises.map((promise) =>
20+
promise.then((possibleErr) => {
21+
if (possibleErr !== null) throw possibleErr
22+
})
23+
)
24+
)
25+
}
26+
27+
module.exports = {
28+
asyncIteratorConcurrentProcess
29+
}

0 commit comments

Comments
 (0)