如何在 node.js 中将函数与 Promise 同步
Posted
技术标签:
【中文标题】如何在 node.js 中将函数与 Promise 同步【英文标题】:how to synchronize functions with promises in node.js 【发布时间】:2018-01-19 16:08:11 【问题描述】:嗨,我正在尝试将我的函数与将 callback 转换为 promise 同步。 我想通过 forEach 循环添加到所有帖子、post.authorName 字段并查询到 user collection。 首先我尝试使用回调,但这是async,我需要sync 工具。 所以我使用promise,但我的结果仍然像回调。 这是我的代码:
var mongo = require('mongodb').MongoClient();
var url = "mongodb://localhost:27017/blog";
var ObjectId = require('mongodb').ObjectID;
var listPosts = function(req, res)
find('post', , 10, author: 1)
.then(function(posts)
var myPosts = posts;
const promises = [];
myPosts.forEach(function(post)
console.log("hi i'm forEach" + '\n');
console.log(post);
console.log('\n');
const promise = new Promise(function(resolve, reject)
getPostAuthorName(post.authorID)
.then(function(postAuthor)
post.authorName = postAuthor;
)
resolve();
);
console.log("i'm end of forEach and this is result:");
console.log(post);
console.log('\n');
promises.push(promise);
);
Promise.all(promises).then(() =>
console.log('i should print at end' + '\n');
);
);
var getPostAuthorName = function(authorID)
return new Promise(function(resolve, reject)
findOne('user', _id: new ObjectId(authorID))
.then(function(result)
console.log("i'm getPostAuthorName" + '\n');
resolve(result.name);
)
)
var find = function(collection, cond = , limit = 0, sort = )
return new Promise(function(resolve, reject)
mongo.connect(url)
.then(function(db)
db.collection(collection)
.find(cond).limit(limit).sort(sort).toArray()
.then(function(result)
resolve(result);
)
)
);
var findOne = function(collection, cond = )
return new Promise(function(resolve, reject)
mongo.connect(url)
.then(function(db)
db.collection(collection).findOne(cond)
.then(function(result)
console.log("i'm findOne" + '\n');
resolve(result);
)
)
)
listPosts();
最后我收到了这个结果:
hi i'm forEach
_id: 59888f418c107711043dfcd6,
title: 'FIRST',
content: 'this is my FIRST post',
timeCreated: 2017-08-07T16:03:13.552Z,
authorID: '5987365e6d1ecc1cd8744ad4'
i'm end of forEach and this is result:
_id: 59888f418c107711043dfcd6,
title: 'FIRST',
content: 'this is my FIRST post',
timeCreated: 2017-08-07T16:03:13.552Z,
authorID: '5987365e6d1ecc1cd8744ad4'
hi i'm forEach
_id: 598d60d7e2014a5c9830e353,
title: 'SECOND',
content: 'this is my SECOND post',
timeCreated: 2017-08-07T16:03:13.552Z,
authorID: '5987365e6d1ecc1cd8744ad4'
i'm end of forEach and this is result:
_id: 598d60d7e2014a5c9830e353,
title: 'SECOND',
content: 'this is my SECOND post',
timeCreated: 2017-08-07T16:03:13.552Z,
authorID: '5987365e6d1ecc1cd8744ad4'
i should print at end
i'm findOne
i'm getPostAuthorName
i'm findOne
i'm getPostAuthorName
为什么函数不能同步运行。 有什么解决办法?
【问题讨论】:
能否将问题简化为具体问题并提供minimal reproducible example? 你只能回答一个问题:promise能保证同步编程吗? 不,当然不是。 Promise 是处理异步的一种更好的方法。您不应该尝试使异步任务同步。 避免使用 Promise 构造函数反模式 -getPostAuthorName
返回一个 Promise,无需将调用包装在一个 Promise 中……反过来,findOne
和 mongo.connect
也返回 Promise,所以,也不需要将它们包装在 Promise 构造函数中
does promise guarantee sync programing
- 恰恰相反,承诺保证异步编程
【参考方案1】:
如果你想将回调转换为承诺,你可以简单地做这样的事情:
function functionWithCallback(params, callback)
[...]
callback(true);
function functionWithPromise(params)
return new Promise((resolve, reject) =>
functionWithCallback(params, (done) =>
if (done)
return resolve(true);
reject(false);
);
);
现在,您可以使用 await 关键字同步 Promise(不要忘记将您的函数 async)。示例:
async function main()
const p1 = functionWithPromise('1');
const p2 = functionWithPromise('2');
await p1;
await p2;
console.log('End');
【讨论】:
【参考方案2】:您的问题在于这个(严重缩进)代码
const promise = new Promise(function(resolve, reject)
getPostAuthorName(post.authorID)
.then(function(postAuthor)
post.authorName = postAuthor;
)
resolve();
);
正确缩进看起来像
const promise = new Promise(function(resolve, reject)
getPostAuthorName(post.authorID)
.then(function(postAuthor)
post.authorName = postAuthor;
)
resolve();
);
所以很明显,resolve
相对于 getPostAuthorName
被“同步”调用 - 但在 getPostAuthorName
的 .then
之前(异步调用)可能被调用 - 因此为什么你的 promises
数组都解决得太早了
所以,如果你移动它
const promise = new Promise(function(resolve, reject)
getPostAuthorName(post.authorID)
.then(function(postAuthor)
post.authorName = postAuthor;
resolve();
)
);
现在,您的代码应该按预期运行
解决代码中的“promise 构造函数反模式” - 以上是一个示例
由于getPostAuthorName
返回一个Promise,所以不需要这样做
const promise = new Promise(function(resolve, reject)
getPostAuthorName(post.authorID)
.then(function(postAuthor)
post.authorName = postAuthor;
resolve(); // resolves to "undefined"
)
);
这相当于
const promise = getPostAuthorName(post.authorID).then(function(postAuthor)
post.authorName = postAuthor;
return; // returns "undefined", just like your resolve() results in
);
所以,删除所有这些反模式,并使用
Promise.all(posts.map(
而不是用 push 构建数组
会产生类似的代码
const mongo = require('mongodb').MongoClient();
const url = "mongodb://localhost:27017/blog";
const ObjectId = require('mongodb').ObjectID;
const listPosts = function(req, res)
find('post', , 10, author: 1)
.then(posts =>
Promise.all(posts.map(post =>
getPostAuthorName(post.authorID)
.then(postAuthor => post.authorName = postAuthor)
))
)
.then(() => console.log('i should print at end' + '\n'));
const getPostAuthorName = authorID =>
findOne('user', _id: new ObjectId(authorID))
.then(result => result.name);
const find = (collection, cond = , limit = 0, sort = ) =>
mongo.connect(url)
.then(db =>
db.collection(collection)
.find(cond)
.limit(limit)
.sort(sort)
.toArray()
);
const findOne = (collection, cond = ) =>
mongo.connect(url)
.then(db =>
db.collection(collection)
.findOne(cond)
);
我想我又掉进了陷阱.. 我敢打赌
posts
不是 javacript 数组 - 在那种情况下我会做一个类似的函数
const makeArray = collection =>
const ret = [];
collection.forEach(item => ret.push(item));
return ret;
;
改变
Promise.all(posts.map(post =>
到
Promise.all(makeArray(posts).map(post =>
【讨论】:
【参考方案3】:如果您不需要,请不要创建承诺!相反,利用链式承诺的能力:
var mongo = require('mongodb').MongoClient();
var url = "mongodb://localhost:27017/blog";
var ObjectId = require('mongodb').ObjectID;
var listPosts = function ()
return find('post', , 10, author: 1)
.then(function (posts)
var promises = posts.map(post => getPostAuthorName(post.authorID));
return Promise.all(promises).then(names => names.map((name, index) =>
var post = posts[index];
post.authorName = name;
return post;
);
);
;
var getPostAuthorName = function(authorID)
return findOne('user', _id: new ObjectId(authorID)).then(author => author.name);
var find = function(collection, cond = , limit = 0, sort = )
return mongo.connect(url)
.then(db => db.collection(db)
.find(cond)
.limit(limit)
.sort(sort)
.toArray()
);
;
var findOne = function(collection, cond = )
return mongo.connect(url).then(db => db.collection(db).findOne(cond));
;
listPosts().then(posts => console.log('Post:', post, ', author: ', post.authorName));
使用new Promise
构造函数创建不必要的承诺称为explicit-construction anti-pattern。
但这不是您代码中的唯一问题:在以下 sn-p 中不必要的承诺中使代码变得如此复杂,以至于您没有意识到在找到作者姓名之前您已解决了承诺:
const promise = new Promise(function(resolve, reject)
getPostAuthorName(post.authorID)
.then(function(postAuthor)
post.authorName = postAuthor;
)
resolve(); // why resolve immediately?
);
相反,它应该是这样的:
const promise = getPostAuthorName(post.authorID)
.then(function(postAuthor)
post.authorName = postAuthor;
);
【讨论】:
以上是关于如何在 node.js 中将函数与 Promise 同步的主要内容,如果未能解决你的问题,请参考以下文章
我们如何在(Node.js 8. 目前)上的 readline.on 函数下使用 promise
如何在 Node.js 流回调中聚合从异步函数生成的 Promise?
如何确保 Node.js 中的这些函数链按顺序执行(使用 Promise)?
如何使用 promise 将数组从我的模块返回到我的 API?异步函数和承诺 - Node.js