因承诺异步/等待而完全丢失

时间:2019-02-13 02:56:44

标签: node.js promise async-await

我正在尝试在NodeJS 11.6.x上开发一个相对简单的测试。我不是真正的开发人员,但有时会尝试进行一些编码。

我的目标是创建一个SQLite数据库,并在每次运行代码时重复一些步骤: 1.删​​除表(如果存在) 2.创建一个表 3.插入N行 4.计算数据库中有多少行 5.关闭数据库

我首先尝试了一种使用回调的基本方法,但无法找出一种方法来进行步骤3(插入N行)并寻找解决方案,promise async / await'pattern'听起来是完成的方法一切。

但是,在重构代码之后,第1步(删除表)没有运行,并且我仍然无法执行第3步(插入N行)并且不知道发生了什么。我也尝试使用没有运气的Promise程序包。

有人可以对此寻求帮助吗?如果可以的话,请解释和提出建议?

预先感谢

编辑:嗯,我不习惯在SO上发布内容,也不知道在这里更新内容的“正确”方法。我以为我应该留下第一个代码作为参考,买我没有了。 现在我觉得自己在那里。所有步骤均按顺序执行。只是我无法使其工作的第3步(插入N行)。或它插入并停止不进入下一个'.then'或仅插入1行,而我无法想象发生了什么。 在代码中,我用“ BUG 1”和“ BUG 2”两行注释。

  1. 如果我俩都被评论,我就会知道发生了什么,它只插入1行,并且不继续答应链
  2. 如果我评论BUG 1并让BUG 2处于活动状态,它将仅插入一行并继续。我想我明白为什么
  3. 如果我评论BUG 2并让BUG 1处于活动状态,它将插入所有行,但不再继续,我想我为什么
  4. 如果我都不评论(我认为应该起作用的方式。则不起作用,并返回一个附加错误“细分错误”

使用以下代码:

const sqlite3 = require('sqlite3')

let db = new sqlite3.Database('./test.db');

waitTime = 1

process.stdout.write('Starting...\n')
var test = new Promise((resolve, reject) => {
    process.stdout.write('Drop Table... ');
    db.run(`DROP TABLE IF EXISTS test`, (err) => {
        if (err) {
            process.stdout.write(`Dropping Error ${err.message}\n`)
            reject()
        } else {
            setTimeout(() => {
                process.stdout.write(`Dropped!\n`)
                resolve()
            }, waitTime)
        }
    })
})
test.then(() => {
    return new Promise((resolve, reject) => {
        process.stdout.write('Create Table... ')
        db.run(`CREATE TABLE IF NOT EXISTS test (data TEXT)`, (err) => {
            if (err) {
                process.stdout.write(`Creating Error ${err.message}\n`)
                reject()
            } else {
                setTimeout(() => {
                    process.stdout.write(`Created!\n`)
                    resolve()
                }, waitTime)
            }
        })
    })
}).then(() => {
    return new Promise((resolve, reject) => {
        process.stdout.write('Insert Line... ')
        lines = 10
        let loop = (async () => {
            for (let i = 0; i < lines; i++) {
                await new Promise(resolve =>
                    db.run(`INSERT INTO test (data) VALUES ('a')`, (err) => {
                        if (err) {
                            process.stdout.write(`Inserting Error ${err.message}\n`)
                            throw (err)
                        } else {
                            setTimeout(() => {
                                // process.stdout.write(`Line ${i} Inserted!\n`)
                                process.stdout.write(`, ${i+1}`)
                                resolve() // BUG 1: if this line is commented, comment it, it will insert only 1 line
                            }, waitTime)
                        }
                    })
                )
            }
        })()
        process.stdout.write(`, IDone\n`)
        resolve() // BUG 2: If this line is commented, the promise chain stops here
    })
}).then(() => {
    return new Promise((resolve, reject) => {
        process.stdout.write('Count Line(s)... ')
        db.all(`SELECT COUNT(*) AS totalLines FROM test`, [], (err, rows) => {
            if (err) {
                process.stdout.write(`Count Error ${err.message}\n`)
                reject()
            } else {
                setTimeout(() => {
                    process.stdout.write(` ${rows[0].totalLines} Count!\n`)
                    resolve()
                }, waitTime)
            }
        })
    })
}).then(() => {
    return new Promise((resolve, reject) => {
        process.stdout.write('Select Line(s)... ')
        db.all('SELECT data FROM test', [], (err, rows) => {
            if (err) {
                process.stdout.write(`Select Error ${err.message}\n`)
                reject()
            } else {
                rows.forEach((row) => {
                    console.log(row.data);
                })
                setTimeout(() => {
                    process.stdout.write(`${rows[0].totalLines} Select!\n`)
                    resolve()
                }, waitTime)
            }
        })
    })
}).then(() => {
    return new Promise((resolve, reject) => {
        process.stdout.write('Close DB... ')
        db.close((err) => {
            if (err) {
                process.stdout.write(`Closing Error ${err.message}\n`)
                reject()
            } else {
                setTimeout(() => {
                    process.stdout.write(`Closed!\n`)
                    resolve()
                }, waitTime)
            }
        })
    })
}).then(() => {
    console.log('Finished')
})

经过@CertainPerformance的出色解释(非常感谢),我得以使其运行。我相信这是现在的“正确”方法。也许有一些更好的方法,但是现在,对我来说还可以,下面是最终代码:

const sqlite3 = require('sqlite3')

let db = new sqlite3.Database('./test.db');

lines = 10

process.stdout.write('Starting... ')
var test = new Promise((resolve, reject) => { process.stdout.write(`Promise Created...!\n`)
        resolve()
})
test.then(() => { process.stdout.write('Drop Table... ')
    return new Promise((resolve, reject) => {
        db.run(`DROP TABLE IF EXISTS test`, (err) => {
            if (err) {
                reject(err)
            } else { process.stdout.write(`Dropped!\n`)
                resolve() }
        })
    })
}).then(() => { process.stdout.write('Create Table... ')
    return new Promise((resolve, reject) => {
        db.run(`CREATE TABLE IF NOT EXISTS test (data TEXT)`, (err) => {
            if (err) {
                reject(err)
            } else {
                process.stdout.write(`Created!\n`)
                resolve() }
        })
    })
}).then(() => { process.stdout.write('Insert Line... ')
    let insertLoop = (async () => {
        for (let i = 0; i < lines; i++) {
            await new Promise(resolve =>
                db.run(`INSERT INTO test (data) VALUES ('a')`, (err) => {
                    if (err) {
                        reject(err)
                    } else { ( i == 0 ) ? process.stdout.write(`${i + 1}`) : process.stdout.write(`, ${i + 1}`)
                        resolve() }
                })
            )
        }
        process.stdout.write(`, Inserted!\n`)
    })()
    return insertLoop
}).then(() => { process.stdout.write('Count Line(s)... ')
    return new Promise((resolve, reject) => {
        db.all(`SELECT COUNT(*) AS totalLines FROM test`, [], (err, rows) => {
            if (err) {
                reject(err)
            } else { process.stdout.write(` ${rows[0].totalLines} Counted!\n`)
                resolve()
            }
        })
    })
}).then(() => { process.stdout.write('Close DB... ')
    return new Promise((resolve, reject) => {
        db.close((err) => {
            if (err) {
                reject(err)
            } else { process.stdout.write(`Closed!\n`)
                resolve()
            }
        })
    })
}).then(() => {
    console.log('Finished')
}).catch((err) => {
    process.stdout.write(`The process did not finish successfully: ${err}`)
})

2 个答案:

答案 0 :(得分:1)

有两个主要问题。首先,在第二个.then中,将loop声明为立即被调用的async函数:这意味着loop将解析为Promise。修剪后的代码如下:

}).then(() => {
    return new Promise((resolve, reject) => {
        let loop = (async () => {
            // do some asynchronus stuff
        })()
        resolve() // BUG 2
    })
}).then(() => {

仅声明Promise不会导致当前线程等待它。上面的代码无法正常工作,原因与该代码立即打印after 的原因相同:

console.log('start');
const prom = new Promise((resolve) => {
  setTimeout(resolve, 500);
});
console.log('after');

您必须在.then(或Promise await)上调用Promise,以便在Promise完成后安排其他操作。或者,如果您当前在.then内,则可以返回 Promise,这意味着下一个.then将在返回后尽快运行Promise解决:

}).then(() => {
      let loop = (async () => {
        // do some asynchronus stuff
    })();
    return loop;
}).then(() => {
    // this block will run once `loop` resolves

请注意,上面缺少new Promise((resolve...构造函数-在.then内部,仅return插入下一个Promise通常是首选方法,因为这意味着更少的代码和avoids an antipattern

当前代码的另一个问题是不会捕获错误。例如,如果您的

db.run(`INSERT INTO test (data) VALUES ('a')`, (err) => {
  if (err) {
    process.stdout.write(`Inserting Error ${err.message}\n`)
    throw (err)
  // else call resolve()

引发错误,此时await的承诺将永远无法解决,也不会拒绝-它将永远处于待处理状态且永远无法实现。您应该将reject作为第二个参数传递给Promise构造函数,并在出现错误(而不是throw)时调用它,例如:

await new Promise((resolve, reject) => {
  db.run(`INSERT INTO test (data) VALUES ('a')`, (err) => {
    if (err) {
      process.stdout.write(`Inserting Error ${err.message}\n`)
      reject(err)
    } else {
      // ...

这样,await的承诺将被拒绝,这意味着整个loop将被拒绝,并且如果返回loop,它将允许{{1 }}来捕获错误,例如:

.catch

请注意,除非每个var test = new Promise((resolve, reject) => { // ... }); test.then(() => { return new Promise(... // ... }) .then(() => { return new Promise(... // .. }) .then(() => { return new Promise(... // .. }) .catch((err) => { process.stdout.write(`The process did not finish successfully:`, err) // handle errors }); 函数调用都需要顺序执行,否则最好一次发出所有请求,并在每个请求完成后立即解决-这样可以大大减少脚本所需的时间跑步。为每个异步调用创建一个db.数组,然后在该数组上调用Promises以得到一个Promise,该Promise将在所有这些Promise.all完成时解决(或者,在Promises个拒绝中的一个> 。例如,对于第二个Promises

.then

幸运的是,代码中没有其他东西可以处理异步循环。

您还可以考虑使用Promisify之类的实用程序功能,它将在每次异步调用时将基于回调的函数转换为Promises,而无需所有额外的}).then(() => { process.stdout.write('Insert Line... ') const proms = Array.from( { length: lines }, (_, i) => new Promise((resolve, reject) => { db.run(`INSERT INTO test (data) VALUES ('a')`, (err) => { if (err) { process.stdout.write(`Inserting Error ${err.message}\n`) reject(err) } else { setTimeout(() => { // process.stdout.write(`Line ${i} Inserted!\n`) process.stdout.write(`, ${i+1}`) resolve() }, waitTime); } }); }) ); return Promise.all(proms); }).then(() => { 样板。

答案 1 :(得分:0)

promisifying db可以对可重用的功能进行进一步的改进,并利用async / await的全部功能而不是mixing it with then

const sqlite3 = require('sqlite3')
let db = new sqlite3.Database('./test.db');

function runDbAsync(sql) {
    return new Promise((resolve, reject) => {
        db.run(sql, (err) => {
            if (err) reject(err);
            else resolve();
        });
    });
}
function getDbAsync(sql, val) {
    return new Promise((resolve, reject) => {
        db.all(`SELECT COUNT(*) AS totalLines FROM test`, [], (err, rows) => {
            if (err) reject(err);
            else resolve(rows);
        });
    });
}
function closeDbAsync() {
    return new Promise((resolve, reject) => {
        db.close((err) => {
            if (err) reject(err);
            else resolve();
        });
    });
}

function write(text) {
    process.stdout.write(text);
}
function writeLn(text) {
    write(text + "\n");
}

async function main() {
    const lines = 10
    writeLn('Starting... ')
    write('Drop Table... ');
    await runDbAsync(`DROP TABLE IF EXISTS test`);
    writeLn(`Dropped!`);
    write('Create Table... ');
    await runDbAsync(`CREATE TABLE IF NOT EXISTS test (data TEXT)`);
    writeLn(`Created!`);
    write('Insert Line... ');
    for (let i = 0; i < lines; i++) {
        await runDbAsync(`INSERT INTO test (data) VALUES ('a')`);
        write( i == 0 `${i + 1}` : `, ${i + 1}`);
    }
    writeLn(`, Inserted!`);
    write('Count Line(s)... ')
    const rows = getDbAsync(`SELECT COUNT(*) AS totalLines FROM test`, []);
    writeLn(` ${rows[0].totalLines} Counted!`)
    write('Close DB... ');
    await closeDbAsync();
    writeLn(`Closed!`);
}

main().then(() => {
    console.log('Finished')
}, err => {
    writeLn(`The process did not finish successfully: ${err}`)
});