読者です 読者をやめる 読者になる 読者になる

リズムのじかん

javascript、typescriptなど中心に書きます。

mongooseでPromise(qとか)

つい最近知ったのですが、mongooseは標準でPromiseの機能持ってるんですね。

Mongoose API v3.8.19

これを使って非同期処理してみます。 あとmongooseのPromiseをqのPromiseに変換します。

User.js

var mongoose = require('mongoose');
var UserSchema = new mongoose.Schema({
    displayName: {
        type: String
    },
    email: {
        type: String
    }
});

UserSchema.static('findOrCreate', function (profile) {
    return Q.Promise(function (resolve, reject) {
        UserDocumentModel.findOne({
            email: profile.email
        })
        .exec()
        .then(function (user) {
            if (user) {
                return resolve(user);
            }
            User.create({
                displayName: profile.displayName,
                email: profile.email
            }).onFulfill(function (user) {
                resolve(user);
            }).onReject(function (err) {
                reject(err);
            });
        });
    });
});

var User = mongoose.model('User', UserSchema);
module.exports = User;

mongooseのModelが持つメソッドexec()とすると(基本的に)Promiseオブジェクトが返されます。

onFulfillのところはthenを使ってもOKですね。

User.create({
    displayName: profile.displayName,
    email: profile.email
}).then(function (user) {
    resolve(user);
}).onReject(function (err) {
    reject(err);
});

thenの第二引数にonRejectの処理を書くと、第一引数のonFullfillでエラーが発生した場合にonRejectに入らないので、onRejectの処理はメソッドチェーンで書くようにしています。

上は例は、新規ユーザを作成する場合にModelのcreateメソッドを使っています。createはPromiseメソッドを返します。

しかし、mongooseのDocument.save()はPromiseを返しません。exec()も使えません。 なのでその場合はコールバック関数を使って以下のように書きます。

UserSchema.static('findOrCreate', function (profile) {
    return Q.Promise(function (resolve, reject) {
        UserDocumentModel.findOne({
            email: profile.email
        })
        .exec()
        .then(function (user) {
            if (user) {
                return resolve(user);
            }
            user = new UserDocumentModel({
                displayName: profile.displayName,
                email: profile.email
            });
            user.save(function(err, user) {
                err ? reject(err) : resolve(user);
            });
        });
    });
});

Userを使う側は以下のようになります。 User.findOrCreateはqのPromiseを返すので、それに対して処理を連結すればOKですね。

app.js

var User = require('./model/User');
passport.use(new TwitterStrategy({
    consumerKey: config.auth.twitter.TWITTER_CONSUMER_KEY,
    consumerSecret: config.auth.twitter.TWITTER_CONSUMER_SECRET,
    callbackURL: config.auth.twitter.callbackURL
  },
  function(token, tokenSecret, profile, done) {
    User.findOrCreate(profile)
      .then(function(user) {
        done(null, user);
      })
      .catch(function(err) {
        done(err);
      });
  }
));

最後にtypescriptで書くと、以下のようになります。

User.ts

/// <reference path="mongoose/mongoose.d.ts" />
/// <reference path="q/Q.d.ts" />

import mongoose = require('mongoose');
import IContact = require('IContact');

var UserSchema: mongoose.Schema = new mongoose.Schema({
  displayName: {
    type: String
  },
  email: {
    type: String
  }
});

UserSchema.static('findOrCreate', (profile: IContact): Q.Promise < User > => {

  return Q.Promise < User > ((resolve, reject) => {
    UserDocumentModel.findOne({
          email: profile.email
      })
      .exec()
      .then(user => {
        if (user) {
          return resolve(user);
        }
        UserDocumentModel.create({
            displayName: profile.displayName,
            email: profile.email
          })
          .onFulfill(user => {
            resolve(user);
          })
          .onReject(err => {
            reject(err)
          });
      });
  });
});

var User = mongoose.model('User', UserSchema);
export = User;