我感兴趣的是理解如何允诺这段代码:
const http = require('http');
const fs = require('fs');
const download = function(url, dest, cb) {
let file = fs.createWriteStream(dest);
const request = http.get(url, function(response) {
response.pipe(file);
file.on('finish', function() {
file.close(cb); // close() is async, call cb after close completes.
});
}).on('error', function(err) { // Handle errors
fs.unlink(dest); // Delete the file async. (But we don't check the result)
if (cb) cb(err.message);
});
};
我对此的第一个看法是:
const http = require('http');
const fs = require('fs');
const download = async (url, dest, cb) => {
let file = fs.createWriteStream(dest);
const request = http.get(url, function(response) {
response.pipe(file);
file.on('finish', function() {
const closed = await file.close(cb); // close() is async, await here?
if (closed) {
// handle cleanup and retval
}
});
}).on('error', function(err) { // Handle errors
const deleted = await fs.unlink(dest); // Delete the file async.
if (!deleted) { ... }
});
};
上面的实施显然是错误的。要移除回调,只使用async/await,正确的方法是什么?
这里有一种方法可以手动将管道操作包装在Promise中。不幸的是,这大部分只是错误处理,以覆盖所有可能发生错误的地方:
const http = require('http');
const fs = require('fs');
const download = function(url, dest) {
return new Promise((resolve, reject) => {
const file = fs.createWriteStream(dest);
// centralize error cleanup function
function cleanup(err) {
reject(err);
// cleanup partial results when aborting with an error
file.on('close', () => {
fs.unlink(dest);
});
file.end();
}
file.on('error', cleanup).on('finish', resolve);
const request = http.get(url, function(response) {
if (response.status < 200 || response.status >= 300) {
cleanup(new Error(`Unexpected Request Status Code: ${response.status}`);
return;
}
response.pipe(file);
response.on('error', cleanup);
}).on('error', cleanup);
});
};
download(someURL, someDest).then(() => {
console.log("operation complete");
}).catch(err => {
console.log(err);
});
这并不是在拒绝之前等待文件在错误条件下被关闭或删除(如果这些清理操作仍然有错误,通常没有什么建设性的操作)。如果需要,只需从这些清理操作的异步回调中调用reject(err)
,或者使用这些函数的fs.promisions
版本并等待它们,就可以很容易地添加它。
有几件事要注意。这主要是错误处理,因为有三个可能的地方您可以有错误,一些错误需要一些清理工作。
>
添加了所需的错误处理。
在OP的原始代码中,他们调用了file.clos()
,但file
是一个流,在WriteStream上没有.clos()
方法。调用.end()
关闭写入流。
您可能还需要检查是否有适当的response.status
,因为http.get()
仍然返回响应对象和流,即使状态类似于4xx或5xx。
下面是我如何将节点样式的回调API重写为异步函数:
const http = require('http');
const fs = require('fs');
async function download (url, dest) {
const response = await new Promise((resolve, reject) => {
http.get(url, resolve).once('error', reject);
});
if (response.status < 200 || response.status >= 300) {
throw new Error(`${responses.status} ${http.STATUS_CODES[response.status]}`);
}
const file = await fs.promises.open(dest, 'w');
try {
for await (const data of response) {
await file.write(data);
}
} catch (error) {
await file.close();
await fs.promises.unlink(dest);
throw error;
}
await file.close();
}
请注意,此方法使用fs.promissions
命名空间中的filehandle
类,以及在readable
流类上定义的symbol.AsyncIterator
接口,该接口允许您使用具有for await.。。of
循环的respons
的data
事件,并通过隐式拒绝基础异步迭代器返回的promise,将错误处理从respons
的error