mongoose + 异步流程处理

    Node.js世界里,绝大部分函数都是遵守这个约定的。

    举个典型的用户登录的例子吧,这是前面见过的

    1. this.findOne({
    2. username: username
    3. }, function (err, user) {
    4. if (err || !user) {
    5. if (err)
    6. console.log(err);
    7. return cb(err, {
    8. code: -1,
    9. msg : username + ' is not exist!'
    10. });
    11. }
    12. bcrypt.compare(password, user.password, function(error, res) {
    13. if (error) {
    14. console.log(error);
    15. return cb(err, {
    16. code: -2,
    17. msg : 'password is incorrect, please check it again!'
    18. });
    19. }
    20. return cb(null, user);
    21. });
    22. });
    23. };

    说明

    • login是有callback的
    • callback里遵守回调约定(err,result)

    这里面findOne有一个回调,bcrypt.compare有一个回调,最后解决过通过login的回调传值回去,这还只是一个简单的逻辑,如果更复杂呢?

    说callback是万恶的,其实一点也不冤枉,它其实是一种约定,但它却被滥用,导致给Node.js带来了长久以来很多人对Node.js的误解,但本身它只是一种形式,并不是最佳实践,所以持这种态度来看待Node.js是不公平的。

    内置Promises:mpromise

    Mongoose 异步操作,像 .save() 和 queries,返回 Promises/A+ conformant promises. This means that you can do things like MyModel.findOne({}).then() and yield MyModel.findOne({}).exec() (if you’re using co).
    这就是说你可以做一些像MyModel.findOne({}).then() 和 yield MyModel.findOne({}).exec()(如果你在用co)

    为了向后兼容,mongoose 4默认返回mpromise promise。

    1. var gnr = new Band({
    2. name: "Guns N' Roses",
    3. members: ['Axl', 'Slash']
    4. });
    5. var promise = gnr.save();
    6. assert.ok(promise instanceof require('mpromise'));
    7. promise.then(function (doc) {
    8. assert.equal(doc.name, "Guns N' Roses");
    9. });

    mongoose查询不是promise。可是它有 yield 和 async/await 的 .then() 方法。如果你需要健全的promise,用.exec()方法。

    1. var query = Band.findOne({name: "Guns N' Roses"});
    2. assert.ok(!(query instanceof require('mpromise')));
    3. // A query is not a fully-fledged promise, but it does have a `.then()`.
    4. // use doc
    5. });
    6. // `.exec()` gives you a fully-fledged promise
    7. var promise = query.exec();
    8. assert.ok(promise instanceof require('mpromise'));
    9. promise.then(function (doc) {
    10. // use doc

    使用其他 Promises 库

    在mongoose 4.1.0更新,在mpromise满足基本使用的情况下,高级用户可能想插入他们喜爱的ES6风格的Promise库如bluebird,或只是使用原生的ES6 promise。设置mongoose.Promise 给你喜欢的ES6风格的promise构造函数然后mongoose会使用它。

    mongoose.Promise属性设置mongoose使用promise。可是,这不影响底层MongoDB驱动。如果你使用底层驱动,例如Mondel.collection.db.insert(),你需要做点额外工作来改变底层promise库。注意,下面的代码假设mongoose >= 4.4.4。

    1. var uri = 'mongodb://localhost:27017/mongoose_test';
    2. // Use bluebird
    3. var options = { promiseLibrary: require('bluebird') };
    4. var db = mongoose.createConnection(uri, options);
    5. Band = db.model('band-promises', { name: String });
    6. db.on('open', function() {
    7. assert.equal(Band.collection.findOne().constructor, require('bluebird'));
    8. });

    bluebird promisifyAll

    promisifyAll是bluebird提供的一个极其方便的api,可以把某个对象上的所有方法都变成返回promise对象方法,在Promise/A+规范里,只要返回promise对象就可以thenable组合完成业务逻辑组合。这是异步流程里比较好的方式。

    如果Model里的方法都能返回Promise对象,那么这些方法就可以理解是乐高积木,在我们写业务逻辑时候,组合这些小模块就好了。

    promisifyAll会把对象上的方法copy一份返回Promise对象的以“方法名Async”为名称的方法

    举例db/promisifyAll.js

    1. var UserModel = {
    2. create: function () {
    3. },
    4. retrieve: function () {
    5. },
    6. update: function () {
    7. },
    8. delete: function () {
    9. }
    10. }
    11. var Promise = require("bluebird");
    12. // Promisify
    13. Promise.promisifyAll(UserModel);
    14. console.dir(UserModel)
    1. $ node promisifyAll.js
    2. {
    3. create: [Function],
    4. retrieve: [Function],
    5. update: [Function],
    6. delete: [Function],
    7. createAsync: [Function],
    8. retrieveAsync: [Function],
    9. updateAsync: [Function],
    10. deleteAsync: [Function]
    11. }

    很明显,create被copy成了createAsync方法,其他亦然。也就是说[原来的方法]变成了[原来的方法Async]

    下面看一下createAsync方法是否是返回Promise对象,按照co源码里判断promise的写法

    返回是true,也就是说createAsync方法是返回的Promise对象。

    1. var mongoose = require('mongoose');
    2. var Promise = require("bluebird");
    3. // 定义Schema
    4. UserSchema = new mongoose.Schema({
    5. username: {// 真实姓名
    6. type: String,
    7. required: true
    8. },
    9. type: String,
    10. required: true
    11. }
    12. });
    13. // 定义Model
    14. var UserModel = mongoose.model('User', UserSchema);
    15. // Promisify
    16. Promise.promisifyAll(UserModel);
    17. Promise.promisifyAll(UserModel.prototype);
    18. // 暴露接口
    19. module.exports = UserModel;

    步骤1:引入bluebird

    1. var Promise = require("bluebird");

    步骤2:给某个对象应用promisifyAll

    1. // Promisify
    2. Promise.promisifyAll(UserModel);
    3. Promise.promisifyAll(UserModel.prototype);

    db/promisify/test.js

    凡是奇技淫巧都有特定场景的应用场景,虽好,但不要滥用。如果转换非常多,会有性能问题。但是在某些场景,比如模型操作上,还是非常方便、高效的。

    1. co(function*() {
    2. var error;
    3. var schema = new Schema({
    4. description: {type: String, required: true}
    5. });
    6. var Breakfast = db.model('breakfast', schema, getCollectionName());
    7. var goodBreakfast = new Breakfast({description: 'eggs & bacon'});
    8. try {
    9. yield goodBreakfast.save();
    10. } catch (e) {
    11. error = e;
    12. }
    13. assert.ifError(error);
    14. var result;
    15. try {
    16. result = yield Breakfast.findOne().exec();
    17. } catch (e) {
    18. error = e;
    19. }
    20. assert.ifError(error);
    21. assert.equal('eggs & bacon', result.description);
    22. // Should cause a validation error because `description` is required
    23. var badBreakfast = new Breakfast({});
    24. try {
    25. yield badBreakfast.save();
    26. } catch (e) {
    27. error = e;
    28. }
    29. assert.ok(error);
    30. assert.ok(error instanceof ValidationError);
    31. done();

    async/await

    支持yield,其实就等于支持async/await了,但目前性能测试还不够好,所以暂时还不推荐使用。