Основното нещо, което наистина пропускате, е, че методите на API на Mongoose също използват "Обещания" , но изглежда просто копирате от документация или стари примери, като използвате обратни извиквания. Решението за това е да преминете към използване само на Promises.
Работа с обещания
Model.find({},{ _id: 1, tweet: 1}).then(tweets =>
Promise.all(
tweets.map(({ _id, tweet }) =>
api.petition(tweet).then(result =>
TweetModel.findOneAndUpdate({ _id }, { result }, { new: true })
.then( updated => { console.log(updated); return updated })
)
)
)
)
.then( updatedDocs => {
// do something with array of updated documents
})
.catch(e => console.error(e))
Освен общото преобразуване от обратни извиквания, основната промяна е използването на Promise.all()
за разрешаване на изхода от Array.map()
се обработва върху резултатите от .find()
вместо за
цикъл. Това всъщност е един от най-големите проблеми във вашия опит след for
всъщност не може да контролира кога асинхронните функции се разрешават. Другият проблем е „смесването на обратни извиквания“, но това е, което обикновено разглеждаме тук, като използваме само Promises.
В рамките на Array.map( )
връщаме обещанието
от извикването на API, свързано към findOneAndUpdate()
което всъщност актуализира документа. Ние също използваме new:true
за действително връщане на променения документ.
Promise.all()код>
позволява на "масив от Promise" да разреши и върне масив от резултати. Виждате ги като updatedDocs
. Друго предимство тук е, че вътрешните методи ще се задействат "паралелно", а не последователно. Това обикновено означава по-бърза резолюция, въпреки че отнема малко повече ресурси.
Имайте предвид също, че използваме „проекцията“ на { _id:1, tweet:1 }
да върне само тези две полета от Model.find()
резултат, защото те са единствените, използвани в останалите повиквания. Това спестява връщането на целия документ за всеки резултат там, когато не използвате другите стойности.
Можете просто да върнете Promiseкод>
от findOneAndUpdate()
, но просто добавям в console.log()
така че можете да видите, че изходът се задейства в тази точка.
Нормалната производствена употреба трябва да мине без него:
Model.find({},{ _id: 1, tweet: 1}).then(tweets =>
Promise.all(
tweets.map(({ _id, tweet }) =>
api.petition(tweet).then(result =>
TweetModel.findOneAndUpdate({ _id }, { result }, { new: true })
)
)
)
)
.then( updatedDocs => {
// do something with array of updated documents
})
.catch(e => console.error(e))
Друга „ощипване“ може да бъде използването на внедряването на „bluebird“ на Promise. map()
, който комбинира общия Array.map()
до Promise
(s) внедряване с възможност за контролиране на "едновременността" на изпълнение на паралелни повиквания:
const Promise = require("bluebird");
Model.find({},{ _id: 1, tweet: 1}).then(tweets =>
Promise.map(tweets, ({ _id, tweet }) =>
api.petition(tweet).then(result =>
TweetModel.findOneAndUpdate({ _id }, { result }, { new: true })
),
{ concurrency: 5 }
)
)
.then( updatedDocs => {
// do something with array of updated documents
})
.catch(e => console.error(e))
Алтернатива на "паралелен" ще бъде изпълнение в последователност. Това може да се има предвид, ако твърде много резултати причиняват твърде много API извиквания и извиквания за обратно записване в базата данни:
Model.find({},{ _id: 1, tweet: 1}).then(tweets => {
let updatedDocs = [];
return tweets.reduce((o,{ _id, tweet }) =>
o.then(() => api.petition(tweet))
.then(result => TweetModel.findByIdAndUpdate(_id, { result }, { new: true })
.then(updated => updatedDocs.push(updated))
,Promise.resolve()
).then(() => updatedDocs);
})
.then( updatedDocs => {
// do something with array of updated documents
})
.catch(e => console.error(e))
Там можем да използваме Array. намали()
да „свърже във верига“ обещанията заедно, позволявайки им да се решават последователно. Обърнете внимание, че масивът от резултати се запазва в обхвата и се разменя с крайния .then()
добавен към края на обединената верига, тъй като имате нужда от такава техника за „събиране“ на резултати от разрешаване на Promises в различни точки от тази „верига“.
Async/Await
В модерни среди като от NodeJS V8.x, което всъщност е текущата LTS версия и е от известно време, всъщност имате поддръжка за async/await
. Това ви позволява да пишете своя поток по-естествено
try {
let tweets = await Model.find({},{ _id: 1, tweet: 1});
let updatedDocs = await Promise.all(
tweets.map(({ _id, tweet }) =>
api.petition(tweet).then(result =>
TweetModel.findByIdAndUpdate(_id, { result }, { new: true })
)
)
);
// Do something with results
} catch(e) {
console.error(e);
}
Или дори евентуално да обработвате последователно, ако ресурсите са проблем:
try {
let cursor = Model.collection.find().project({ _id: 1, tweet: 1 });
while ( await cursor.hasNext() ) {
let { _id, tweet } = await cursor.next();
let result = await api.petition(tweet);
let updated = await TweetModel.findByIdAndUpdate(_id, { result },{ new: true });
// do something with updated document
}
} catch(e) {
console.error(e)
}
Отбелязвайки също, че findByIdAndUpdate()
може също да се използва като съвпадение на _id
вече се подразбира, така че нямате нужда от цял документ на заявка като първи аргумент.
Групово писане
Като последна бележка, ако изобщо не се нуждаете от актуализираните документи в отговор, тогава bulkWrite()
е по-добрият вариант и позволява записите като цяло да се обработват на сървъра в една заявка:
Model.find({},{ _id: 1, tweet: 1}).then(tweets =>
Promise.all(
tweets.map(({ _id, tweet }) => api.petition(tweet).then(result => ({ _id, result }))
)
).then( results =>
Tweetmodel.bulkWrite(
results.map(({ _id, result }) =>
({ updateOne: { filter: { _id }, update: { $set: { result } } } })
)
)
)
.catch(e => console.error(e))
Или чрез async/await
синтаксис:
try {
let tweets = await Model.find({},{ _id: 1, tweet: 1});
let writeResult = await Tweetmodel.bulkWrite(
(await Promise.all(
tweets.map(({ _id, tweet }) => api.petition(tweet).then(result => ({ _id, result }))
)).map(({ _id, result }) =>
({ updateOne: { filter: { _id }, update: { $set: { result } } } })
)
);
} catch(e) {
console.error(e);
}
Почти всички комбинации, показани по-горе, могат да бъдат променени в това като bulkWrite()
методът приема "масив" от инструкции, така че можете да конструирате този масив от обработените API извиквания от всеки метод по-горе.