如何在设置module.exports之前等待异步功能完成

时间:2019-09-13 17:26:14

标签: javascript node.js mongodb express asynchronous

我正在尝试让我的应用基于从数据库加载的数据(mongodb + mongoose)创建路由;

这是我目前拥有的:

var app = express();

var articleRoute = require('./article.js');
var Articles = require('./models/articles.js');
Articles.get((err, articles) => {
    if (err) return console.log('error');

    //loop through all articles
    articles.forEach(article=> {
        app.use('/'+article.slug, articleRoute);

    });
});

module.exports = app;

文章已成功获取,但不会作为模块包含在应用中.exports是在从数据库获取文章之前设置的。

如果我改为将该行放在回调中,如下所示:

var app = express();

var articleRoute = require('./article.js');
var Articles = require('./models/articles.js');
Articles.get((err, articles) => {
    if (err) return console.log('error');

    //loop through all articles
    articles.forEach(article=> {
        app.use('/'+article.slug, articleRoute);

    });

    module.exports = app;
});

然后,该应用程序实际上似乎已经退出,没有设置任何导出,并且我得到了此错误消息:TypeError: app.set is not a function,该错误消息是从我的主脚本中这样调用的:

var app = require('../app');
app.set('port', 3000);

1 个答案:

答案 0 :(得分:1)

首先,我强烈建议您在顶层路线与条塞相匹配的设计中使用。这样就不会给您提供唯一的名称空间,该名称空间不会与文章标签冲突,从而为您的网站页面,Ajax路由等创建正常路径。在构建和维护网站以放置文章的所有动态路由时,通常这样会更清洁某些静态路径下的子弹,例如:

/article

然后,文章条目与运行您的网站的其余路径之间不会有任何冲突。我还要指出,它更像是“ REST风格”,因为您在路径顶部指定了所请求的资源类型,然后将资源名称添加为第二级路径。


  

如何在设置module.exports之前等待异步功能完成

在当前版本的node.js中,您不能等待异步操作完成后再设置导出。不能做有人提议在将来的node.js版本中允许使用该版本,但现在不在这里。

导出和导入是同步的。因此,异步获取的值尚不能用于导出或导入。

通常的解决方法是,导出一个可以调用的方法,该方法可以返回一个promise或接受一个回调,然后在该方法中获取异步值,然后调用方使用返回的promise或传入回调和异步值通过promise或回调传递回去。


第一版可以工作

仅供参考,如果您不添加优先于异步操作完成时添加的路由的通用路由,则您的第一个版本仍然可以使用。在异步代码完成其工作之前,这些路由仅在app对象上不可用。可以导出一个app对象,该对象在导出后将添加一些路由。该构造有效,尽管您的服务器可以在没有附加所有路由的情况下运行,并且在articles.forEach()完成后,将添加其余路由(我认为这花费不到几秒钟,因此可能不是很大)。


第二个版本不起作用

我想您知道,您的第二个版本根本不起作用,因为异步分配module.exports不起作用。呼叫者将已经抓住了空的module.exports对象,以后再分配时将看不到您的app


导出主要启动文件调用的异步方法

解决此问题的经典方法是导出添加路线的方法:

const app = express();

const articleRoute = require('./article.js');
const Articles = require('./models/articles.js');
const promisify = require('util').promisify;
const Articles.getPromise = promisify(Articles.get);

function assignArticleRoutes() {
    return Articles.getPromise().then(articles => {
        //loop through all articles
        articles.forEach(article=> {
            app.use('/'+article.slug, articleRoute);
        });
    }).catch(err => {
        console.log("Error loading article routes", err);
        throw err;
    });
}

module.exports = {app, assignArticleRoutes};

然后,从主启动文件中调用该方法:

const {app, assignArticleRoutes} = require('../app');
app.set('port', 3000);

assignArticleRoutes().then(() =>> {
    // start server only after all routes have been added
    app.listen(app.get('port'));
}).catch(err => {
    // decide what to do here if article routes can't be loaded
});

对所有文章使用单一动态路由

让问题更具灵活性的另一种思考方式是在路线上使用静态商品前缀,以对其进行预先配置,然后根据需要加载商品。之所以可行,是因为您已经对所有商品路线使用了通用的请求处理程序:

const app = express();

const articleRoute = require('./article.js');
const Articles = require('./models/articles.js');
const promisify = require('util').promisify;
const Articles.getPromise = promisify(Articles.get);

// load articles and save promise so we can use the promise later    
const articlesP = Articles.getPromise().then(articles => {
    const articleSet = new Set(articles);
    return articleSet;
}).catch(err => {
    console.log("Error loading article routes", err);
    throw err;
});

app.use("/article/:articleSlug", function(req, res, next) {
    articlesP.then(articleSet => {
        // dynamically check if this route matches a known article slug
        if (articleSet.has(req.param.articleSlug)) {
            articleRoute(req, res, next);
        } else {
            // no match found, just call next()
            next();
        }
    });
});

module.exports = app;

然后,您必须编辑articleRoute()才能期望实际的文章名称不是路径的开头,而是req.param.articleSlug或第二个路径元素。

注意,这还可以让您动态地添加和删除文章,而无需重新启动服务器。