nodejs mssql 和 promises 混淆
nodejs mssql and promises confusion
我对 javascript 中的 Promises 非常陌生,所以这个问题是为了帮助我弄清楚为什么我在使用 Promise 时遇到错误(以奇怪的顺序)。最重要的是,我正忙于第一次使用 ms-sql 存储库、sinonjs 和 restify,所以这也无济于事
这个问题与我之前问过的一个问题有关
在上面的问题中,我需要在@robertklep 的帮助下成功完成 SQL 数据库的存根。然而,作为完整性检查,我想检查端点是否仍然像以前一样 returning 我期望的数据,所以我取消了存根。现在我收到以下错误,我不知道为什么:
[Error: Can't set headers after they are sent.]
测试:
'use strict';
var expect = require('chai').expect,
request = require('supertest'),
chance = require('chance').Chance(),
server = require('../server'),
sinon = require('sinon'),
select = require('../../helpers/data_access/select'),
read_file = require('../../helpers/read_file');
describe("/account_types", function () {
// before(function (done) {
// sinon
// .stub(select, "query_list")
// .returns([{id: "test"}]);
//
// sinon
// .stub(select, "query_single")
// .returns({id: "test"});
//
// sinon
// .stub(read_file, "get_file_contents")
// .returns("Some content to get excited about");
//
// done();
// });
//
// after(function (done) {
// select
// .query_list
// .restore();
// select
// .query_single
// .restore();
// read_file
// .get_file_contents
// .restore();
//
// done();
// });
it('GET 200 List', function (done) {
request(server.baseURL)
.get('/api/v1/account_types')
.set('Accept', 'application/json')
.expect('Content-Type', 'application/json')
.expect(200)
.end(function (err, res) {
/* istanbul ignore if */
if (err)
return done(err);
expect(res.body).to.not.be.null;
expect(res.body).to.not.be.undefined;
expect(res.body).to.be.an('Array');
expect(res.body.length).to.be.above(0);
//expect(select.query_list).to.have.been.calledOnce;
return done();
});
});
it('GET 200 Single', function (done) {
//var param = chance.random();
request(server.baseURL)
.get('/api/v1/account_type/' + 1)
.set('Accept', 'application/json')
.expect('Content-Type', 'application/json')
.expect(200)
.end(function (err, res) {
/* istanbul ignore if */
if (err)
return done(err);
expect(res.body).to.not.be.null;
expect(res.body).to.not.be.undefined;
expect(res.body).to.be.an('Object');
expect(res.body.toString().length).to.be.above(0);
return done();
});
});
it('POST 200 Single', function (done) {
var param = chance.random();
request(server.baseURL)
.post('/api/v1/account_type')
.set('Accept', 'application/json')
.send({id: param})
.expect('Content-Type', 'application/json')
.expect(200)
.end(function (err, res) {
/* istanbul ignore if */
if (err)
return done(err);
expect(res.body).to.be.an('object');
expect(res.body).to.include.keys('result');
expect(res.body.result).to.equal('post account_type : ' + param.toString());
return done();
});
});
it('PUT 200 Single', function (done) {
var param = chance.random();
request(server.baseURL)
.put('/api/v1/account_type')
.set('Accept', 'application/json')
.send({id: param})
.expect('Content-Type', 'application/json')
.expect(200)
.end(function (err, res) {
/* istanbul ignore if */
if (err)
return done(err);
expect(res.body).to.be.an('object');
expect(res.body).to.include.keys('result');
expect(res.body.result).to.equal('put account_type : ' + param.toString());
return done();
});
});
it('DELETE 200 Single', function (done) {
var param = chance.random();
request(server.baseURL)
.delete('/api/v1/account_type/' + param)
.set('Accept', 'application/json')
.expect('Content-Type', 'application/json')
.expect(200)
.end(function (err, res) {
/* istanbul ignore if */
if (err)
return done(err);
expect(res.body).to.be.an('object');
expect(res.body).to.include.keys('result');
expect(res.body.result).to.equal('delete account_type : ' + param.toString());
return done();
});
});
});
端点:
var select = require('../helpers/data_access/select'),
read_file = require('../helpers/read_file'),
format = require('string-format');
const db_config_name = 'db.goaml';
module.exports = function (server) {
server.get('/api/v1/account_types', function (req, res, next) {
var query = read_file.get_file_contents('path to query');
select.query_list(db_config_name, query, function (err, records) {
console.log('test 1');
if (err != null) {
return next(err);
}
res.send(records);
//return next(); <-- EDIT: Removed as per Mike Perrenoud answer
});
//return next(); <-- EDIT: Removed as per Mike Perrenoud answer
});
server.get('/api/v1/account_type/:id', function (req, res, next) {
var query =
format(read_file.get_file_contents('path to query'), req.params.id);
select.query_single(db_config_name, query, function (err, records) {
console.log('test 2');
if (err != null) {
return next(err);
}
res.send(records[0]);
//return next(); <-- EDIT: Removed as per Mike Perrenoud answer
});
//return next(); <-- EDIT: Removed as per Mike Perrenoud answer
});
server.post('/api/v1/account_type', function (req, res, next) {
res.send({'result': 'post account_type : ' + req.body.id});
return next();
});
server.put('/api/v1/account_type', function (req, res, next) {
res.send({'result': 'put account_type : ' + req.body.id});
return next();
});
server.del('/api/v1/account_type/:id', function (req, res, next) {
res.send({'result': 'delete account_type : ' + req.params.id});
return next();
});
};
select.js:
var sql = require('mssql'),
config = require('./configs/config');
module.exports = {
query_list: function (config_name, sql_query, callback) {
return query(config_name, sql_query, true, callback);
},
query_single: function (config_name, sql_query, callback) {
return query(config_name, sql_query, false, callback);
}
};
function query(config_name, sql_query, isList, callback) {
var db_config = config.get(config_name),
connection = new sql.Connection(db_config);
connection.connect(function () {
new sql.Request(connection)
.query(sql_query)
.then(function (records) {
console.log('test 3');
callback(null, isList ? records : records[0]);
connection.close();
})
.catch(function (err) {
console.log('test 4');
console.log(err);
callback(err, null);
});
});
// EDIT for answer:
// This catch is not allowed. IE the connection isn't a promise. Thus
// when the code responded with something valid, it reached this part
// after that and realised this is an illegal statement. As a result
// it throws an error after the res.send was already hit and showing the
// initial error I reported.
// .catch(function (err) {
// console.log('test 5');
// callback(err, null);
// });
}
当我 运行 gulp 任务测试上述代码时,我得到以下输出:
C:\Code\JS\general_admin_service>gulp test
[16:12:47] Using gulpfile C:\Code\JS\general_admin_service\gulpfile.js
[16:12:47] Starting 'test'...
[16:12:47] Finished 'test' after 62 ms
/account_types
1) GET 200 List
2) GET 200 Single
test 3
test 1
test 4
[Error: Can't set headers after they are sent.]
test 1
√ POST 200 Single
test 3
test 2
test 4
[Error: Can't remove headers after they are sent.]
test 2
√ PUT 200 Single
√ DELETE 200 Single
3 passing (400ms)
2 failing
1) /account_types GET 200 List:
Error: expected 200 "OK", got 500 "Internal Server Error"
at Test._assertStatus (C:\Code\JS\general_admin_service\node_modules\supertest\lib\test.js:232:12)
at Test._assertFunction (C:\Code\JS\general_admin_service\node_modules\supertest\lib\test.js:247:11)
at Test.assert (C:\Code\JS\general_admin_service\node_modules\supertest\lib\test.js:148:18)
at assert (C:\Code\JS\general_admin_service\node_modules\supertest\lib\test.js:127:12)
at C:\Code\JS\general_admin_service\node_modules\supertest\lib\test.js:124:5
at Test.Request.callback (C:\Code\JS\general_admin_service\node_modules\superagent\lib\node\index.js:831:3)
at Stream.<anonymous> (C:\Code\JS\general_admin_service\node_modules\superagent\lib\node\index.js:1049:12)
at Unzip.<anonymous> (C:\Code\JS\general_admin_service\node_modules\superagent\lib\node\utils.js:108:12)
2) /account_types GET 200 Single:
Error: expected 200 "OK", got 500 "Internal Server Error"
at Test._assertStatus (C:\Code\JS\general_admin_service\node_modules\supertest\lib\test.js:232:12)
at Test._assertFunction (C:\Code\JS\general_admin_service\node_modules\supertest\lib\test.js:247:11)
at Test.assert (C:\Code\JS\general_admin_service\node_modules\supertest\lib\test.js:148:18)
at assert (C:\Code\JS\general_admin_service\node_modules\supertest\lib\test.js:127:12)
at C:\Code\JS\general_admin_service\node_modules\supertest\lib\test.js:124:5
at Test.Request.callback (C:\Code\JS\general_admin_service\node_modules\superagent\lib\node\index.js:831:3)
at Stream.<anonymous> (C:\Code\JS\general_admin_service\node_modules\superagent\lib\node\index.js:1049:12)
at Unzip.<anonymous> (C:\Code\JS\general_admin_service\node_modules\superagent\lib\node\utils.js:108:12)
events.js:142
throw er; // Unhandled 'error' event
^
Error: 2 tests failed.
令我困惑的是,如果我查看console.log's
的输出,很明显代码进入了sql.request
的"successful" then
块并调用回调。我可以确认,如果我添加一个 console.log
来检查 records 参数的值,其中实际上有数据返回。
在这一点上 res.send
得到命中,但是随后在 sql.query
的 catch
块中发现了一个错误,此时是混乱的地方:
如果成功了 return,为什么会被击中?
代码的哪一部分在响应测试。 IE。为什么 headers 在设置后被更改? (next(err)
可能与它有关吗?)
如前所述,这个问题和我上一个问题有很大关系。我不得不更改代码以满足我的单元测试。但是,我觉得有必要进行健全性检查并确保数据仍按预期进行 returned,在这一点上,我认为 ms-sql 使用的 Promise 框架妨碍了return将值用于测试。 (我不是在质疑 Promise 框架的价值)。
我希望有人知道我做错了什么。
编辑:
所以我对发生的事情有点困惑。使用原样的代码,如果我取消注释测试的 before
和 after
部分,测试将失败,看起来它与回调有关。
如果我完全删除回调并在 sql.Request
的 then
中有一个 return 并且在 catches
中有一个 throw err
那么我的测试通过.
但是,如果我直接进行没有存根的测试,此设置将不会 return 值。
发生这种情况是因为您正在 return next();
在 来自 SQL 服务器的回调完成之前执行。这是一个问题的原因是,虽然它本质上是异步的,但您总体上正在执行一组同步操作。
发生的事情是 res.send(records);
失败,因为 next()
已经被调用并且服务器已经返回 200
。
最后,您不需要 res.send(records);
之后的 return next();
因为 res.send(records);
结束了进程并且 returns 响应了。
我对 javascript 中的 Promises 非常陌生,所以这个问题是为了帮助我弄清楚为什么我在使用 Promise 时遇到错误(以奇怪的顺序)。最重要的是,我正忙于第一次使用 ms-sql 存储库、sinonjs 和 restify,所以这也无济于事
这个问题与我之前问过的一个问题有关
在上面的问题中,我需要在@robertklep 的帮助下成功完成 SQL 数据库的存根。然而,作为完整性检查,我想检查端点是否仍然像以前一样 returning 我期望的数据,所以我取消了存根。现在我收到以下错误,我不知道为什么:
[Error: Can't set headers after they are sent.]
测试:
'use strict';
var expect = require('chai').expect,
request = require('supertest'),
chance = require('chance').Chance(),
server = require('../server'),
sinon = require('sinon'),
select = require('../../helpers/data_access/select'),
read_file = require('../../helpers/read_file');
describe("/account_types", function () {
// before(function (done) {
// sinon
// .stub(select, "query_list")
// .returns([{id: "test"}]);
//
// sinon
// .stub(select, "query_single")
// .returns({id: "test"});
//
// sinon
// .stub(read_file, "get_file_contents")
// .returns("Some content to get excited about");
//
// done();
// });
//
// after(function (done) {
// select
// .query_list
// .restore();
// select
// .query_single
// .restore();
// read_file
// .get_file_contents
// .restore();
//
// done();
// });
it('GET 200 List', function (done) {
request(server.baseURL)
.get('/api/v1/account_types')
.set('Accept', 'application/json')
.expect('Content-Type', 'application/json')
.expect(200)
.end(function (err, res) {
/* istanbul ignore if */
if (err)
return done(err);
expect(res.body).to.not.be.null;
expect(res.body).to.not.be.undefined;
expect(res.body).to.be.an('Array');
expect(res.body.length).to.be.above(0);
//expect(select.query_list).to.have.been.calledOnce;
return done();
});
});
it('GET 200 Single', function (done) {
//var param = chance.random();
request(server.baseURL)
.get('/api/v1/account_type/' + 1)
.set('Accept', 'application/json')
.expect('Content-Type', 'application/json')
.expect(200)
.end(function (err, res) {
/* istanbul ignore if */
if (err)
return done(err);
expect(res.body).to.not.be.null;
expect(res.body).to.not.be.undefined;
expect(res.body).to.be.an('Object');
expect(res.body.toString().length).to.be.above(0);
return done();
});
});
it('POST 200 Single', function (done) {
var param = chance.random();
request(server.baseURL)
.post('/api/v1/account_type')
.set('Accept', 'application/json')
.send({id: param})
.expect('Content-Type', 'application/json')
.expect(200)
.end(function (err, res) {
/* istanbul ignore if */
if (err)
return done(err);
expect(res.body).to.be.an('object');
expect(res.body).to.include.keys('result');
expect(res.body.result).to.equal('post account_type : ' + param.toString());
return done();
});
});
it('PUT 200 Single', function (done) {
var param = chance.random();
request(server.baseURL)
.put('/api/v1/account_type')
.set('Accept', 'application/json')
.send({id: param})
.expect('Content-Type', 'application/json')
.expect(200)
.end(function (err, res) {
/* istanbul ignore if */
if (err)
return done(err);
expect(res.body).to.be.an('object');
expect(res.body).to.include.keys('result');
expect(res.body.result).to.equal('put account_type : ' + param.toString());
return done();
});
});
it('DELETE 200 Single', function (done) {
var param = chance.random();
request(server.baseURL)
.delete('/api/v1/account_type/' + param)
.set('Accept', 'application/json')
.expect('Content-Type', 'application/json')
.expect(200)
.end(function (err, res) {
/* istanbul ignore if */
if (err)
return done(err);
expect(res.body).to.be.an('object');
expect(res.body).to.include.keys('result');
expect(res.body.result).to.equal('delete account_type : ' + param.toString());
return done();
});
});
});
端点:
var select = require('../helpers/data_access/select'),
read_file = require('../helpers/read_file'),
format = require('string-format');
const db_config_name = 'db.goaml';
module.exports = function (server) {
server.get('/api/v1/account_types', function (req, res, next) {
var query = read_file.get_file_contents('path to query');
select.query_list(db_config_name, query, function (err, records) {
console.log('test 1');
if (err != null) {
return next(err);
}
res.send(records);
//return next(); <-- EDIT: Removed as per Mike Perrenoud answer
});
//return next(); <-- EDIT: Removed as per Mike Perrenoud answer
});
server.get('/api/v1/account_type/:id', function (req, res, next) {
var query =
format(read_file.get_file_contents('path to query'), req.params.id);
select.query_single(db_config_name, query, function (err, records) {
console.log('test 2');
if (err != null) {
return next(err);
}
res.send(records[0]);
//return next(); <-- EDIT: Removed as per Mike Perrenoud answer
});
//return next(); <-- EDIT: Removed as per Mike Perrenoud answer
});
server.post('/api/v1/account_type', function (req, res, next) {
res.send({'result': 'post account_type : ' + req.body.id});
return next();
});
server.put('/api/v1/account_type', function (req, res, next) {
res.send({'result': 'put account_type : ' + req.body.id});
return next();
});
server.del('/api/v1/account_type/:id', function (req, res, next) {
res.send({'result': 'delete account_type : ' + req.params.id});
return next();
});
};
select.js:
var sql = require('mssql'),
config = require('./configs/config');
module.exports = {
query_list: function (config_name, sql_query, callback) {
return query(config_name, sql_query, true, callback);
},
query_single: function (config_name, sql_query, callback) {
return query(config_name, sql_query, false, callback);
}
};
function query(config_name, sql_query, isList, callback) {
var db_config = config.get(config_name),
connection = new sql.Connection(db_config);
connection.connect(function () {
new sql.Request(connection)
.query(sql_query)
.then(function (records) {
console.log('test 3');
callback(null, isList ? records : records[0]);
connection.close();
})
.catch(function (err) {
console.log('test 4');
console.log(err);
callback(err, null);
});
});
// EDIT for answer:
// This catch is not allowed. IE the connection isn't a promise. Thus
// when the code responded with something valid, it reached this part
// after that and realised this is an illegal statement. As a result
// it throws an error after the res.send was already hit and showing the
// initial error I reported.
// .catch(function (err) {
// console.log('test 5');
// callback(err, null);
// });
}
当我 运行 gulp 任务测试上述代码时,我得到以下输出:
C:\Code\JS\general_admin_service>gulp test
[16:12:47] Using gulpfile C:\Code\JS\general_admin_service\gulpfile.js
[16:12:47] Starting 'test'...
[16:12:47] Finished 'test' after 62 ms
/account_types
1) GET 200 List
2) GET 200 Single
test 3
test 1
test 4
[Error: Can't set headers after they are sent.]
test 1
√ POST 200 Single
test 3
test 2
test 4
[Error: Can't remove headers after they are sent.]
test 2
√ PUT 200 Single
√ DELETE 200 Single
3 passing (400ms)
2 failing
1) /account_types GET 200 List:
Error: expected 200 "OK", got 500 "Internal Server Error"
at Test._assertStatus (C:\Code\JS\general_admin_service\node_modules\supertest\lib\test.js:232:12)
at Test._assertFunction (C:\Code\JS\general_admin_service\node_modules\supertest\lib\test.js:247:11)
at Test.assert (C:\Code\JS\general_admin_service\node_modules\supertest\lib\test.js:148:18)
at assert (C:\Code\JS\general_admin_service\node_modules\supertest\lib\test.js:127:12)
at C:\Code\JS\general_admin_service\node_modules\supertest\lib\test.js:124:5
at Test.Request.callback (C:\Code\JS\general_admin_service\node_modules\superagent\lib\node\index.js:831:3)
at Stream.<anonymous> (C:\Code\JS\general_admin_service\node_modules\superagent\lib\node\index.js:1049:12)
at Unzip.<anonymous> (C:\Code\JS\general_admin_service\node_modules\superagent\lib\node\utils.js:108:12)
2) /account_types GET 200 Single:
Error: expected 200 "OK", got 500 "Internal Server Error"
at Test._assertStatus (C:\Code\JS\general_admin_service\node_modules\supertest\lib\test.js:232:12)
at Test._assertFunction (C:\Code\JS\general_admin_service\node_modules\supertest\lib\test.js:247:11)
at Test.assert (C:\Code\JS\general_admin_service\node_modules\supertest\lib\test.js:148:18)
at assert (C:\Code\JS\general_admin_service\node_modules\supertest\lib\test.js:127:12)
at C:\Code\JS\general_admin_service\node_modules\supertest\lib\test.js:124:5
at Test.Request.callback (C:\Code\JS\general_admin_service\node_modules\superagent\lib\node\index.js:831:3)
at Stream.<anonymous> (C:\Code\JS\general_admin_service\node_modules\superagent\lib\node\index.js:1049:12)
at Unzip.<anonymous> (C:\Code\JS\general_admin_service\node_modules\superagent\lib\node\utils.js:108:12)
events.js:142
throw er; // Unhandled 'error' event
^
Error: 2 tests failed.
令我困惑的是,如果我查看console.log's
的输出,很明显代码进入了sql.request
的"successful" then
块并调用回调。我可以确认,如果我添加一个 console.log
来检查 records 参数的值,其中实际上有数据返回。
在这一点上 res.send
得到命中,但是随后在 sql.query
的 catch
块中发现了一个错误,此时是混乱的地方:
如果成功了 return,为什么会被击中?
代码的哪一部分在响应测试。 IE。为什么 headers 在设置后被更改? (next(err)
可能与它有关吗?)
如前所述,这个问题和我上一个问题有很大关系。我不得不更改代码以满足我的单元测试。但是,我觉得有必要进行健全性检查并确保数据仍按预期进行 returned,在这一点上,我认为 ms-sql 使用的 Promise 框架妨碍了return将值用于测试。 (我不是在质疑 Promise 框架的价值)。
我希望有人知道我做错了什么。
编辑:
所以我对发生的事情有点困惑。使用原样的代码,如果我取消注释测试的 before
和 after
部分,测试将失败,看起来它与回调有关。
如果我完全删除回调并在 sql.Request
的 then
中有一个 return 并且在 catches
中有一个 throw err
那么我的测试通过.
但是,如果我直接进行没有存根的测试,此设置将不会 return 值。
发生这种情况是因为您正在 return next();
在 来自 SQL 服务器的回调完成之前执行。这是一个问题的原因是,虽然它本质上是异步的,但您总体上正在执行一组同步操作。
发生的事情是 res.send(records);
失败,因为 next()
已经被调用并且服务器已经返回 200
。
最后,您不需要 res.send(records);
之后的 return next();
因为 res.send(records);
结束了进程并且 returns 响应了。