Как да обработваме API заявки с верига от MongoDB заявки

Вие пишете бекенд услуга за вашето уеб приложение и трябва да извлечете данни от вашия mongo клъстер. Проблемът е, че не искате вашата заявка да бъде върната по синхронен начин — вместо това искам да бъдат изпълнени mongo заявки, да получа върнатия резултат, да го обработя и да върна JSON към предния край, когато е готов.

Искате да отговаряте на стандартите за javascript ES6/7, за да поддържате кода на приложението си и да останете уместни; трябва да внедрите promises, async и await функционалност, обвита около вашите db.collection mongo заявки.

Разрушаване на процеса

Преди пълния пример и обяснението на пълноценна асинхронна заявка за API, искам накратко да преразгледам обещанията и използването на async/await в javascript.

Promise

Обещанията ни дават начин да се справим с асинхронната обработка по по-синхронен начин. Те представляват стойност, с която можем да се справим в някакъв момент в бъдещето; в крайна сметка ще бъде върнато или решено.

Ако извикаме обещание и го console.log, ще бъдем посрещнати с чакащо обещание. Обещанието все още не е решено. Той е по средата на завършване на това, което кодираме, за да направи. Когато се разреши, ще можем да извлечем данните, които първоначално възнамерявахме да обещаем да ни върнат.

Обещанията са неизменни, манипулаторът не може да се променя. Също така ни е гарантирано да получим върната стойност: или това, което сме възнамерявали, или грешка.

Ние ще напишем нашите обещания във функциите на ES6 и след това ще ги извикаме асинхронно с помощта на await и async.

Как изглежда едно обещание? Нещо като това:

var myPromise = () => (
    new Promise((resolve, reject) => {
        
        //do something, fetch something....
        //you guessed it, mongo queries go here.
        db.collection('your_collection').find(...)
        //I can continue to process my result inside my promise
        .then(function(result){
            //another query can be called based on my result...
            return updatedResult;
        })
         //This promise may take a while...
         .then(function(result){
             //post processing, non related mongo code...
             //when you are ready, you can resolve the promise.
             resolve(result);
        });
    })
);

Обърнете внимание на втория манипулатор (обикновено наречен reject). Това е функция за извикване, за да отхвърли обещанието, ако не може да разреши бъдещата стойност.

Можем да разширим предишния псевдокод, за да отчетем отхвърлянето на нежелани данни:

//when you are ready you can resolve the promise.
var somethingWentWrong = (dataReturned == null);
(somethingWentWrong)
   ? reject('something messed up') 
   : resolve(result);

Сега нека преминем към асинхронна обработка на нашите обещания.

async / await

Както можете да видите, ключовите думи async и await отсъстват от нашия Promise код. Използваме ги, за да конфигурираме асинхронни функции, които извикват нашето обещание и чакат да завърши, като това:

var callMyPromise = async () => {
    var result = await (myPromise());
    return result;
};

Вижте колко просто беше това? Някои статии онлайн правят процеса да изглежда доста сложен. Не е - отделете декларациите на обещанията си и вашите асинхронни функции. Направете нещата лесни за четене и надграждане; вашият екип ще го оцени.

Така че последното парче от пъзела е да съберем всичко заедно, за да можем най-накрая да върнем нашата заявка за API, която изглежда по следния начин:

callMyPromise().then(function(result) {
    //close mongo client
    client.close();
    //feel free to process your final result before sending
    //it back to your front end
    //return the API request
    res.json(result);
});

Сглобяване на всичко

Нека съберем всичко, през което току-що преминахме, за да създадем пълна заявка за API. Да кажем, че използвам Express като моя бекенд услуга:

router.post('/api/get_data', (req, res, next) => {
   try {
      MongoClient.connect(connectionStr, mongoOptions, function(err, client) {
       assert.equal(null, err);
       const db = client.db('db');
      
       //Step 1: declare promise
      
       var myPromise = () => {
         return new Promise((resolve, reject) => {
        
            db
             .collection('your_collection')
             .find({id: 123})
             .limit(1)
             .toArray(function(err, data) {
                 err 
                    ? reject(err) 
                    : resolve(data[0]);
               });
         });
       };

       //Step 2: async promise handler
       var callMyPromise = async () => {
          
          var result = await (myPromise());
          //anything here is executed after result is resolved
          return result;
       };
 
       //Step 3: make the call
       callMyPromise().then(function(result) {
          client.close();
          res.json(result);
       });
    }); //end mongo client
   
   } catch (e) {
     next(e)
   }
});
module.exports = router;

Някои точки относно този пример:

  • Целият процес е обвит в try catch, така че мога да се справя с всички възникнали грешки.
  • res.json връща резултата от моите данни като JSON обект.

Сега, това, което направихме тук, е смесване на async и await функции с нашите then() функции за обратно извикване. Можем обаче да изберем да използваме само един от тях.

Така че защо използвахме и двете в горния пример? Тъй като демонстрира как можем да изчакаме асинхронна функция да разреши, които също се третират като обещания.

Нека проучим как можем да оптимизираме примера по-долу.

Използване на обещания без async / await

Сега всъщност можем да премахнем ключовите думи async / await тук заедно със стъпка 2 и просто да продължим с блок then() след извикването на обещанието:

//Step 1: declare promise
var myPromise = () => {
   ...
};

//omitting step 2

//step 3: make the call
myPromise().then(res => {
   client.close();
   res.json(result);
};

Наистина, това е по-чист синтаксис. В действителност вашето обещание ще бъде импортирано от файл с външни модули, следователно стъпка 3 ще бъде единственият код, присъстващ на ниво вашите маршрути.

Използване само на async / await

Така че защо да използваме ключовите думи async / await в този пример?

Вижте пренаписания пример по-долу.

Ние декларираме нашето обещание като стъпка 1, както преди, но след това използваме await, за да поставим на пауза изпълнението, докато myPromise бъде разрешен, преди да затворим mongo клиента и да разрешим извикването на API.

Забележете, че ключовата дума async сега се използва във функцията за обратно извикване на рутера на първия ред:

router.post('/api/get_data', async (req, res, next) => {
try {
MongoClient.connect(connectionStr, mongoOptions, function(err, client) {
   assert.equal(null, err);
   const db = client.db('db');
      
   //Step 1: declare promise
      
    var myPromise = () => {
       return new Promise((resolve, reject) => {
        
          db
          .collection('your_collection')
          .find({id: 123})
          .limit(1)
          .toArray(function(err, data) {
             err 
                ? reject(err) 
                : resolve(data[0]);
           });
       });
    };
   //await myPromise
   var result = await myPromise();
   //continue execution
   client.close();
   res.json(result);
}); //end mongo client
} catch (e) {
   next(e)
}
});
module.exports = router;

Кой стил предпочитате? then() може да изглежда по-четлив за някои, докато await може да изглежда по-чист и минимален код за по-опитния програмист.

Готови ли сте да продължите?

Продължавам да проучвам концепциите за обещания, async и await в последващи статии, които разширяват концепциите в тази статия до създаване на библиотека от базирани на обещания експорти за вашите API извиквания.