搜尋

首頁  >  問答  >  主體

express - node.js中操作mongodb, 如何解决异步的问题?

我在使用node.js + express + mongodb做一个英语课堂测试系统, 其中有一个学生管理界面. 新增一个学生, 有以下字段: name(名称), age(年龄), grade(年级)等, 于是我封装了一个判断函数, 用于判断前端所传递的数据是否有误:

  // 检查所传入的数据是否有效
  function check_info(_id, name, age, grade) {
    if ("" == name) {
      return {status: false, msg: '名称不可为空'};
    }
    age = parseInt(age);
    grade = parseInt(grade);
    if (isNaN(age)) {
      return {status: false, msg: '年龄必须为整数'};
    }
    if (isNaN(grade)) {
      return {status: false, msg: '年级必须为整数'};
    }
    console.log("1");
    if (_id) {
      collection.find({_id: _id, name: name}).count(function (err, count) {
        if (1 != count) {
          return {status: false, msg: '编辑情况下, 名称不可更改!'};
        }
      });
    } else {
      console.log("2");
      collection.find({name: name}).count(function (err, count) {
        console.log("3");
        if (0 != count) {
          return {status: false, msg: '新增情况下, 数据库中已经存在此名称!'};
        }
      });
    }
    console.log("4");
    return {status: true, msg: '正确'};
  }

在这里, 很明显, 由于find数据库时候, 采用的是异步操作, 导致永远返回{status: true, msg: '正确'};

问题:

  1. 在实际项目中, 是如何解决这种情况的? 我查了数据库, 貌似还没有同步的解决方案出现?

  2. 我没有使用mongoose, 使用的是mongodb的本身api:

var express = require('express');
var router = express.Router();
var init = function (callback) {
var MongoClient = require('mongodb').MongoClient;
  var url = 'mongodb://localhost:27017/test_system';
  MongoClient.connect(url, function (err, db) {
    var collection = db.collection('students');
    callback(collection);
  });
};
迷茫迷茫2873 天前483

全部回覆(4)我來回復

  • 怪我咯

    怪我咯2017-04-17 15:40:05

    處理過程的確是非同步處理,對於大多數語言/框架來說,預設都是同步處理,需要非同步的時候開一個執行緒進行非同步處理,所以非同步的數量是很容易控制的。然而 Node 的實作有點不一樣,由於 JavaScript 是單執行緒程序,比較耗時的操作為了阻止阻礙都會採用非同步處理。

    備註:單線程的非同步操作,咋個說都啕大扭,看原理介紹一大堆,我直接理解為:主線程負責UI,要幹所有其它事情都必須在其它線程上異步操作。 Android 在朝這個方向發展,已經要求網路操作等不能在主執行緒進行了,所以這種方式還是可以理解的。

    非同步處理就一定涉及到訊息通知,或者說線程間的互動。在 Node 中是透過回調來實現的(因為是單線程的非同步…糾結),或者說得高大上一點,是透過觀察者模式來實現的。對於 ES5 來說,使用回調嵌回調是最直接的方式。當然隨著 Node 的使用越來越廣,出現了不少的函式庫用來解決這個問題,統稱為 Promise 函式庫吧。很顯然,ECMA 也意識到了這個問題,所以 ES6/ES2015 中直接內建了 Promise 實作。

    function init() {
        var MongoClient = require("mongodb").MongoClient;
        var url = "mongodb://localhost:27017/test_system";
        return new Promise((resolve, reject) => {
            MongoClient.connect(url, function(err, db) {
                if (err) {
                    throw err;  // 和调用 reject(err) 效果类似
                }
                var collection = db.collection("students");
                resolve(collection);
            });
        });
    }
    
    function asyncCheckInfo(collection, _id, name, age, grade) {
        return new Promise((resolve, reject) => {
            if ("" == name) {
                reject({ status: false, msg: "名称不可为空" });
                return;
            }
    
            // ....
            // 省略的这部分代码的改造与上面类似
            // ....
    
            // 下面是两个异步调用,注意异步调用结束(即在回调中)一定要调用 resolve 或者 reject
            // 为了统一处理,定义一个局部函数
            function deal(err, isError, errMsg) {
                if (err) {
                    reject({ status: false, msg: err.message });
                    return;
                }
    
                if (isError) {
                    reject({ status: false, msg: errMsg });
                    return;
                }
    
                resolve({ status: true, msg: "正确" });
            }
    
            if (_id) {
                collection.find({ _id: _id, name: name }).count(function(err, count) {
                    deal(err, 1 !== count, "编辑情况下, 名称不可更改!");
                });
            } else {
                collection.find({ name: name }).count(function(err, count) {
                    deal(err, 0 !== count, "新增情况下, 数据库中已经存在此名称!");
                });
            }
    
            // 上面所有分支都已经有 resolve 或 reject 调用了,搞定
        });
    }

    呼叫

    init().then(connection => {
        return asyncCheckInfo(connection, _id, name, age, grade);
    }).then(jo => {
        // asyncCheckInfo 中通过 resolve 给的对象
        // jo.status === true
    }, jo => {
        // asyncCheckInfo 中通过 reject 给的对象
        // jo.status === false
        console.log(jo.msg);
    });

    雖然用 Promise 好多了,但是寫起來還是比較繁瑣,尤其是使用的庫沒使用 Promise 封裝的時候,要自己去改造是件痛苦的事情。

    所以ES7 準備實現async/await,把異步調用同步化(僅指寫法上同步化,並非真正的同步),另外有一些庫,比如KOA,也嘗試通過generator 來實現異步鏈,想了解可以參考KOA 的文檔。

    回覆
    0
  • PHP中文网

    PHP中文网2017-04-17 15:40:05

    1.asyncjs
    2.bluebird
    3.等node 7.X發布 用 await/async

    回覆
    0
  • PHP中文网

    PHP中文网2017-04-17 15:40:05

    這個問題可能是剛接觸node.js的時候很難習慣的問題,是的,在你心中確實像流水線一樣在執行,但是默認的方式只有回調函數(你給他一個盒子讓他在執行完以後把你想要的東西放在這個盒子裡),然而回調函數是不會先等待他執行完後再執行別的,所以你順序的形式獲得結果是不行的,當你查詢已經執行的時候馬上就執行了下一句,直到最後一句,所以每次結果都一樣。因此你需要在回呼函數裡執行下一步操作,但以後你會發現步驟太多就會嵌套很深,就會了解到promise+generator以及async+await了。

    如果你需要返回,可以接受一個回調函數,你再呼叫這個函數把你的資料傳出去。

    你這個例子我幫你形象化吧。

    你爸爸給你一個壺要你買醬油,你讓弟弟去買來了一裝在你的壺裡,你這個查詢就是讓你弟弟去打醬油的過程,他打完回來就是回調函數執行完的時候,然後你再執行下一步,你心裡應該是這個過程了,但是你剛讓你弟弟去打醬油,接著就把壺交給你爸了,當然每次油壺都是空的咯。

    沒有電腦在身邊,也不能寫程式給你了,程式碼應該是最直覺的。

    回覆
    0
  • 天蓬老师

    天蓬老师2017-04-17 15:40:05

    定義一個物件儲存回傳的數據,然後傳回這個物件給前端

    回覆
    0
  • 取消回覆