주니어 개발자였으면 좋았을 프라미스 가이드

22246 단어 javascriptbeginners
첫 번째 "약속 {보류 중}"에서 걸려 넘어진 것을 기억하십니까? 마법처럼 작동하는지 확인하기 위해 .then 을 입력하시겠습니까? 이 기사는 에 대한 마지막 기사 이후에 JS 에코시스템의 기본 벽돌을 다시 배우는 과정에서 여러분을 안내할 것입니다.

이 기사의 범위는 기본적으로 Promise에 대한 이해(이를 작성하고 인식하는 방법)에서 async/await 다이빙까지입니다. 아래 목차가 있는 부분으로 자유롭게 건너뛸 수 있습니다.

면책 조항 : 우리는 약속의 '숨겨진' 부분을 보지 않을 것입니다. 숨김이란 Javascript 엔진이 호출 스택 내에서 어떻게 처리하는지를 의미합니다.

목차



  • Necessary background informations
  • So, what are Promises ?
  • Why do we need them ?


  • Let's dig into the code
  • How can I use them ?
  • How can I recognize a Promise I didn't write ?


  • Some additionnal informations
  • Async / Await


  • 필요한 배경 정보



    The MDN web documentation 약속에 대한 정의는 다음과 같습니다.
    Promise는 Promise가 생성될 때 반드시 알려지지 않은 값에 대한 프록시입니다.
    이를 통해 핸들러를 비동기 작업의 최종 성공 값 또는 실패 이유와 연결할 수 있습니다.

    저처럼 명확하지 않은 경우 나와 함께있어 모든 것을 정리할 것입니다. 약속합니다 😉.

    그래서, 약속은 무엇입니까?



    Now that we let this bad joke behind, let's dive straigt into it.

    First, the basics. Javascript is a synchronous and mono-threaded language. It means that all your code will execute in the order it's write, and it only has one call stack. To keep it simple, we'll stand that JS is a language where eveything happen in order, without any external add-ons.

    Promises are a way to execute certain pieces of code asynchronously, meaning they'll be executed behind the scenes, while the rest of the synchronous code is done.

    왜 필요합니까?



    Let's take a real life exemple with two waiters. To keep it simple, our waiters are responsible from taking the orders, and delivering the dishes froms the kitchen to the clients.

    Our first waiter will be the synchronous one (like if Javascript promises never existed). He would take the order, give it to the kitchen, wait for the dishes to be ready, and finally serve them, and so on. Kinda awkward and inefficient.

    Our second waiter will handle things asynchronously. He'll take the orders from the clients and give it to the kitchen. By the time the dishes are ready, he will go do something else and come back later for the dishes whenever they are ready.

    This is exactly what's happening in JS. The kitchen will give a promise to the waiter that the dishes will be ready sometime in the future.

    If promises never existed, all our code that requires an external HTTP call will block the rest from executing until the call is over, just like our first waiter was stuck at the kitchen in between orders.

    Concretely, on a website, you couldn't see the text or the shape until everyting has been loaded, leading to enormous loading time, or weird-looking pages. And nowadays, nobody want to wait more than 2 or 3 seconds for a website to load, right ?

    코드를 파헤쳐보자



    어떻게 사용할 수 있습니까?



    Now, let's write our first Promise. A very basic one would look like this :

    new Promise((resolve, reject) => {
      // Your asynchronous code
    });
    

    A promise always take a function with two arguments : resolve and reject. When the promise must return the result, we call resolve with the results. If something wrong happened, let's say we're missing some ingredients, the whole promise is compromised, we must cancel the order and get something different from the client, this is where we call reject.

    // Your promise can be stored in a variable, or returned within a function
    const preparingDishes = new Promise((resolve, reject) => {
      const isIngredientMissing = false;
    
      const dishes = [
        // Everything that the client ordered, it would be filled up as soon as one is ready
      ];
    
      // But if an ingredient is missing, immedialty call back the waiter to inform the clients
      if (isIngredientMissing) return reject('An ingredient is missing !');
    
      // We use setTimeout to mimic an HTTP call
      setTimeout(() => {
        // 10 seconds later, once the dishes are ready, send them to the waiter
        return resolve(dishes);
      }, 10000);
    });
    

    Now, what is the value of preparingDishes ?

    console.log(preparingDishes); // <---- What will appear in the console ?
    

    If you're already familiar with promises, you answered "Promise {pending}" and you're right.
    If you were expecting [ ] don't worry, we'll figure this out.

    console.log(preparingDishes); // <---- Promise {pending}
    

    Why ? If we keep with our waiter exemple, we say to the kitchen : Hey, I have a new order, prepare it, and we didn't let the necessary preparation time, so the kitchen answer It's not ready ! We're still preparing it. The promise is pending.

    How do we access the value then ?

    Do you remember the MDN definition ? It allows you to associate handlers, the keywords here is handlers. Let's go back to our previous exemple, but let's get it to work for real this time.

    const preparingDishes = new Promise((resolve, reject) => {
      // See the code above
    });
    
    // Now that our promise is created, let's trigger it, and then read the results
    preparingDishes
      .then((dishes) => {
        // dishes is a arbitrary name, usually it's called result
    
        /* Within this function, we can access the result of the promise. The parameter will be the value you gave to the resolve.
        You are guaranted that anything you put in here, will execute when the promise is fullfilled (succesfull) */
        callWaiter(dishes);
      })
      .catch((error) => {
        // In case an error happened, this handler will catch the return value inside your above reject or any error that could happen in your promise code
        if (error === 'An ingredient is missing !') {
          sendWaiterBacktoClients();
        }
      })
      .finally(() => {
        // This one will execute anything that you put inside, either the promise succeed or not
    
        // For example, wether the kitchen succeed preparing the dishes or not, they'll have to start the next one
        prepareNextDishes();
      });
    

    As you must have noticed by now, the .then, .catch and .finally are the handlers MDN is talking about. Each will execute under different circumstances as stated above.
    Please take note that attaching all the handlers isn't mandatory, you could only attach a .then for exemple (but I wouldn't recommended it), or only a .then and a .catch, which is what you'll use most of the time.

    Nice ! Now that we write our first promise, and used it properly, let's go to the last part, where I personnaly struggled a lot with.

    내가 작성하지 않은 약속을 어떻게 알 수 있습니까?



    Let's say you're onboarding on a new project, you must get used to the codebase, but you're not sure what is asynchronous or not. Or even better, you're trying to figure out how to use a thrid-party library without the documentation.

    You have 2 solutions to figure if a specific piece of code is a promise or not.

    • If you're using VSC as your text editor, you can let your mouse over the piece of code you're interested in. In the pop-up that's appearing, you can analyse what is the value. If this is a promise, you will see it as :
      Promise<any>
    

    Don't let the 'any' keyword instill doubt, this is some fancy Typescript, and will be replace with any value the promise is returning.

    • If a .then is hanging around, the variable before it is 100% a promise (unless the code you're stepping in is already broke 😕).

    추가 정보



    As we saw it, a promise is always followed by specific handlers. They are all meant to be used with promises, using them with a string for example will lead to errors where JS will complain that .then is not a function.

    Even if promises are very cool, nesting promises in promises can lead to what we call the callback hell. Imagine that a specific piece of code result from a serie of promises, you'll have to wait the previous promises to be completed to access the value, and so on, leading to scary things like this :

    gettingOrder.then((order) => {
      giveOrderToKitchen(order).then((dishes) => {
        serveClient(dishes).then(() => {
          startOver();
        });
      });
    });
    

    I purposely omitted all the .catch here, the readability already took a shoot. To solve this, we do have a powerful tool, async/await.

    비동기/대기



    First, I would like to clarify something that took me a long time to understand, async/await is nothing more than syntactic sugar for promises.

    Let's rewrite our first promise :

    // Your function must be async to use the await keyword
    async function waiterJob() {
      try {
        const dishes = await preparingDishes;
      } catch (error) {
        // Handle the error
      }
    }
    

    So, couple things changed here, we now have a function with the keyword async, a try/catch block, and the await keyword. Even if we still don't get what happened here yet, we can already say that it's way cleaner.

    Here are the rules for using async/await :

    • The await keyword can only be used within an async function.

      • It replace the .then, you must not use it in conjuncution with .then/.catch. Exemple :
      // ☠️ Don't
      await preparingDishes.then((dishes) => {});
    
      // Don't do this kind of no sense either, it would work, but it's way more readable as a full async/await
      preparingDishes.then(async (dishes) => {
        await something();
      });
    
    • Making a function async will enforce it's return value to be a promise. In our exemple the waiterJob function is a promise that you'll have to await aswell when calling it.
    • Using await is a bit more tricky than the .then. If you want, you could await in front of a string, JS won't complain unlike the .then. Be very careful of not using it everywhere.
      • Using await where you don't need to won't lead to bug in itself, but the async function around it can, because it can break you app if you don't handle it properly.
    • To handle errors properly, you must wrap your promise call within a try/catch block. Our previous .catch will be handled here, and anything that break inside the try will be caught.

    Hold on a second, you said that all async function must be awaited ? This is endless !

    Well, not really. Most of the time in your real apps, you'll have a synchronous function upward which nobody depends on, like an initialization function.

    If you don't have any, you could write what we call IIFE 기본적으로 자체 호출 함수이므로 다음을 수행할 수 있습니다.

    // How can I achieve this ?
    
    import prepareTable from './';
    
    const table = await prepareTable; // <---- Error, it's not wrapped within an async function
    
    // ----------------------------------------
    
    import prepareTable from './';
    
    (async function () {
      const table = await prepareTable; // 👍
    })();
    


    다음은 async/await로 리팩토링된 마지막 예제와 같습니다.

    async function waiterJob() {
      try {
        const order = await gettingOrder();
        const dishes = await giveOrderToKitchen(order);
        await serveClient(dishes);
        startOver();
      } catch (error) {
        // Handle the error
      }
    }
    


    여기에서 마무리하고 약속으로 시작하는 데 도움이 되는 모든 것을 보았습니다. 경험이 더 많은 개발자이고 누락된 것이 있다고 생각되면 언제든지 이에 대한 의견을 추가하십시오.

    Othrys website에서 원본 기사를 찾을 수 있으며 이 기사에 대해 토론하려면 여기에서 나를 팔로우하거나 태그를 지정할 수 있습니다.

    좋은 웹페이지 즐겨찾기