覆盖promise所解析的值的好方法是什么?

时间:2018-01-21 14:00:58

标签: javascript node.js ecmascript-6 promise

我正在编写一个Node.js脚本,用测试数据集填充SQL数据库。

在下面的代码片段中说明的promise链中(实际代码有点毛茸茸),函数insertData()要求db对象从前一阶段传递。但是,前一阶段dropAndCreateTables()内的异步调用使用db对象,但不返回它。我想出了一个解决方案,它将dropAndCreateTables()内的promise包装到另一个解析为db对象的promise对象中。但是:

  • 我听说在非库代码中使用Promise()构造函数是反模式,可能导致细微且难以诊断的错误
  • 我听说嵌套then() - 链也是反模式
  • 我不允许忽略来自promiseDrop的错误(例如,如果表格不存在,我就不在乎)
  • 很难看

问题:

  • 是否有更简单,更好,更社交的方式来覆盖承诺的回报价值? (在这种情况下,使用Promise.all()创建)
  • 有没有办法以不会发生此问题的方式重构我的代码? (也就是说,我不排除" XY问题的可能性"在这里)

代码:

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)

4 个答案:

答案 0 :(得分:4)

(一些相当重要的笔记中途和后面的答案,请一直读到最后。)

  

是否有更简单,更好,更被社会接受的方式来覆盖承诺的回报价值? (在这种情况下,使用Promise.all()创建)

是的,您只需从then处理程序返回一个值,然后返回承诺then返回:

return Promise.all([promiseDrop, promiseCreate])
    .then(() => db);

then(和catch)创建承诺。链中的每个链接都可以转换结果。 thencatch会根据回调中的内容返回新的承诺来解决或拒绝:

  • 如果他们的回调抛出,则承诺拒绝并抛出错误
  • 如果他们的回调返回一个不可用的值(例如,promise),则promise将使用该值解析
  • 如果他们的回调返回了一个值,那么promise就会自己解决那个问题 - 如果它解决则解决,如果拒绝就拒绝
  

我听说在非库代码中使用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)

您可能会忽略dbdropAndCreateTables解析.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)