我正在编写一个Node.js脚本,用测试数据集填充SQL数据库。
在下面的代码片段中说明的promise链中(实际代码有点毛茸茸),函数insertData()
要求db
对象从前一阶段传递。但是,前一阶段dropAndCreateTables()
内的异步调用使用db
对象,但不返回它。我想出了一个解决方案,它将dropAndCreateTables()
内的promise包装到另一个解析为db
对象的promise对象中。但是:
Promise()
构造函数是反模式,可能导致细微且难以诊断的错误then()
- 链也是反模式promiseDrop
的错误(例如,如果表格不存在,我就不在乎)问题:
Promise.all()
创建)代码:
const dropAndCreateTables = (db, startClean) => {
if(startClean) {
const sqlDrop = fs.readFileSync('drop.sql').toString()
const promiseDrop = db.raw(sqlDrop)
const sqlCreate = fs.readFileSync('create.sql').toString()
const promiseCreate = db.raw(sqlCreate)
/********* Problems here? ************************************/
return new Promise((resolve, reject) => { // Ew?
Promise.all([promiseDrop, promiseCreate])
.then(() => {
resolve(db) // Override the returned value
})
.catch(reject)
})
}
return Promise.resolve(db)
}
initDB({ debug: false })
.then((db) => {
return dropAndCreateTables(db, START_CLEAN) // Without my hack this does not return `db`
})
.then((db) => {
return insertData(db, DO_UPSERT) // This needs the `db` object
})
.then(() => {
console.info(`\n${timestamp()} done`)
})
.catch(handleError)
答案 0 :(得分:4)
(一些相当重要的笔记中途和后面的答案,请一直读到最后。)
是否有更简单,更好,更被社会接受的方式来覆盖承诺的回报价值? (在这种情况下,使用
Promise.all()
创建)
是的,您只需从then
处理程序返回一个值,然后返回承诺then
返回:
return Promise.all([promiseDrop, promiseCreate])
.then(() => db);
then
(和catch
)创建承诺链。链中的每个链接都可以转换结果。 then
和catch
会根据回调中的内容返回新的承诺来解决或拒绝:
我听说在非库代码中使用
Promise()
构造函数是反模式,可能会导致细微且难以诊断的错误
区别不是库代码与非库代码,而是代码之间尚未承诺使用代码和代码。如果您已经承诺使用,则几乎不需要使用new Promise
。更多:What is the explicit promise construction antipattern and how do I avoid it?
我听说嵌套then() - 链也是反模式
您几乎不需要嵌套 then
链,因为链中的每个链接都已经具有转换通过它的结果的方法。所以:
// Unnecessary nesting
doSomething()
.then(a => {
return doSomethingElse(a * 2)
.then(b => b * 3);
})
.catch(e => { /*...handle error...*/ });
可以更具惯用性和简单写法:
doSomething()
.then(a => doSomethingElse(a * 2))
.then(b => b * 3);
.catch(e => { /*...handle error...*/ });
有没有办法以不会发生此问题的方式重构我的代码? (也就是说,我不排除“XY问题”的可能性)
本身不是X / Y,但您在该代码中遇到问题:无法保证在drop
之前发生create
!因此,不要同时启动它们并让它们并行运行并使用Promise.all
查看结果,请确保这些操作按顺序顺序发生:
// Fairly minimal changes
const dropAndCreateTables = (db, startClean) => {
if(startClean) {
const sqlDrop = fs.readFileSync('drop.sql').toString()
return db.raw(sqlDrop)
.then(() => {
const sqlCreate = fs.readFileSync('create.sql').toString()
return db.raw(sqlCreate);
})
.then(() => db);
}
return Promise.resolve(db)
}
但是,我不会使用同步文件I / O.代替
const promisify = require("utils").promisify;
const readWithPromise = promisify(fs.readFile);
然后
const dropAndCreateTables = (db, startClean) => {
if(startClean) {
const getDrop = readWithPromise('drop.sql'); // Start this first
const getCreate = readWithPromise('create.sql'); // Then start this
return getDrop
.then(dropSql => db.raw(dropSql)) // Got the drop SQL, run it
.then(() => getCreate) // Make sure we have the create SQl
.then(createSql => db.raw(createSql)) // Run it
.then(() => db);
}
return Promise.resolve(db)
}
注意我们如何避免忙于等待I / O,并且我们可以通过读取create SQL来重叠DB的drop操作。
答案 1 :(得分:1)
在返回另一个承诺时,您不需要调用Promise
构造函数,您可以像下面那样编写:
return Promise.all([promiseDrop, promiseCreate])
.then(() => db)
.catch(error => {
// handle the error or rethrow it
})
答案 2 :(得分:1)
您可能会忽略db
从dropAndCreateTables
解析.then((db) => {
return dropAndCreateTables(db, START_CLEAN).then(Promise.resolve(db));
})
:
visibility: hidden;
答案 3 :(得分:1)
你不应该让dropAndCreateTables返回db promise,它没有真正的用例。所以:
return Promise.all([promiseDrop, promiseCreate]);
就够了。现在是链接部分:
initDB({ debug: false }).then(async (db) => {
await dropAndCreateTables(db, START_CLEAN);
await insertData(db, DO_UPSERT);
console.info(`\n${timestamp()} done`)
}).catch(handleError)