我有兴趣了解如何Promisfy此代码块:
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?
答案 0 :(得分:3)
这是一种手动将管道操作包装在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.promises
版本并等待它们,就可以轻松地添加它。
一些注意事项。这主要是错误处理,因为可能会在三个位置出现错误,并且需要为某些错误进行一些清理工作。
添加了必需的错误处理。
在OP的原始代码中,它们称为file.close()
,但是file
是流,并且writeStream上没有.close()
方法。您调用.end()
以关闭写入流。
您可能还需要检查适当的response.status
,因为http.get()
仍会返回响应对象并流式传输,即使状态为4xx或5xx之类。
答案 1 :(得分:3)
这是我将您的节点样式回调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.promises
命名空间中的FileHandle
类,以及Symbol.asyncIterator
流类上定义的Readable
接口,这使您可以使用data
的{{1}}事件和一个for await...of
循环,并将错误处理从response
的{{1}}事件传播到error
块隐式拒绝底层异步迭代器返回的promise。