IT/JavsScript

비동기 처리 방식 알아보기 (Promise, Callback, Async, Await)

라임웨일 2022. 7. 14. 10:52
반응형

💡 비동기 프로그래밍(Asynchronous)

Callback
Promises
async & await

 

👍 Callback 함수란?

  • 다른 함수가 실행을 끝낸 뒤 실행(call back)되는 함수(⇒ 나중에 호출되는 함수)를 말합니다.  영어의 의미로 Call(호출) back(뒤에)의 개념으로 이해하면 쉽습니다.
  • 다시 말해 코드를 통해 명시적으로 호출하는 함수가 아니라, 함수를 등록해 놓은 후 어떤 이벤트가 발생했거나 특정 시점에 도달했을 때 시스템에서 호출하는 함수입니다.
  • 파라미터로 함수를 전달받아, 함수의 내부에서 실행됩니다.

 

✔ 콜백 함수 (Callback Function) 사용 이유

  • 자바스크립트에서 비동기적 프로그래밍을 할 수 있기 때문입니다.
  • 자바스크립트는 싱글스레드를 사용하는데, 멈춤을 방지해줍니다. 즉 블록킹을 방지하여 싱글스레드가 논 블록킹으로 동작하게 합니다.
👌 싱글스레드 : 싱글스레드는 한 번에 하나의 작업만 수행할 수 있다. 
👌 싱글스레드로 어떻게 비동기 작업이 가능할까?

자바스크립트의 메인스레드인 이벤트 루프가 싱글스레드이기 때문에 자바스크립트를 싱글스레드 언어라고 부르는데 자바스크립트는 이벤트 루프만 독립적으로 실행하지는 않고, 웹 브라우저나 NodeJS 같은 멀티 쓰레드 환경에서 실행됩니다..
즉, 동시성을 보장하는 비동기, 논 블록킹 작업들은 JavaScript 엔진을 구동하는 웹 브라우저, NodeJS (런타임 환경)에서 담당합니다.

보다 자바스크립트의 동작 원리를 알고 싶다면 
자바스크립트 실행 컨텍스트 알아보기  https://whales.tistory.com/129
이벤트 루프 알아보기 : https://whales.tistory.com/130

 

✔ 콜백 함수(Callback Function) 사용 유형

  1. 익명 함수 사용
    • 콜백 함수는 이름 없는 '익명의 함수'를 사용합니다. 함수의 내부에서 실행되기 때문에 이름을 붙이지 않아도 됩니다.
  2. 함수의 이름(만) 넘기기
    • 자바스크립트는 null과 undefined 타입을 제외하고 모든 것을 객체로 다룹니다. 함수를 변수 또는 다른 함수의 변수처럼 사용할 수 있고 함수를 콜백 함수로 사용할 경우, 함수의 이름만 넘겨주면 됩니다.
  3. 전역 변수, 지역변수를 콜백 함수의 파라미터로 전달 가능
    • 전역 변수(Global Variable) : 함수 외부에서 선언된 변수
    • 지역변수 (Local Variable) : 함수 내부에서 선언된 변수

 

✔ 콜백 함수(Callback Function) 주의할 점 및 단점

- 콜백 지옥 (Callback Hell)

  • 콜백 지옥(Callback Hell)은 콜백 함수를 익명 함수로 전달하는 과정이 반복되어 코드의 들여 쓰기 수준이 감당하기 힘들 정도로 깊어지는 현상입니다.
  • Callback 같은 경우 함수의 처리 순서를 보장하기 위해서 함수를 중첩하게 사용되는 경우가 발행해 콜백 헬이 발행하는 단점 에러 처리가 힘들다는단점이 있습니다.
  • 주로 이벤트 처리나 서버 통신과 같은 비동기적인 작업을 수행하기 위해 이런 형태가 자주 등장하는데, 가독성이 떨어지면서 코드를 수정하기 어려워집니다.
  • 비동기적인 작업을 수행하기 위해 콜백 함수를 익명 함수로 전달하는 과정에서 생기는 콜백 지옥을 Promise, async/await, Generator 등을 사용해 방지할 수 있다.

 

콜백 지옥을 보여주는 예시 이미지

 

👍 Promise란?

  • 프로미스는 자바스크립트 비동기 처리에 사용되는 객체입니다
  • Promise 객체는 비동기 작업이 맞이할 미래의 완료 또는 실패와 그 결괏값을 나타냅니다.
  • 싱글스레드인 자바스크립트에서 비동기 처리를 위해 사용한 Callback 함수의 에러/예외처리의 어려움중첩으로 인한 복잡도 증가라는 단점을 해결하기 위해 프로미스 객체를 ES6에서 언어적 차원으로 지원합니다. 
Promise가 콜백을 대체하는 것은 아니지만, 콜백을 예측 가능한 패턴으로 사용할 수 있게 하며 Promise 없이 콜백만 사용했을 때 예상치 못한 동작을 막아주거나 찾기 힘든 버그를 상당수 해결해줍니다.
 

 

✔ Promise 만들기

  • Promise는 인스턴스 생성처럼 new 키워드를 통해 하나의 객체를 생성합니다.
  • 객체이기 때문에 변수 등에 할당하여 활용이 가능합니다.
  • Promise는 하나의 콜백 함수(여기서의 의미는 비동기 콜백 함수가 아님)를 인자로 받습니다. new Promise가 생성되는 즉시 인자로 받아지는 함수도 즉시 실행되며, 그래서 이 함수를 executor, 실행자 함수라고도 부릅니다.
  • 해당 실행자(executor) 함수는 다시 2개 함수(resolve, reject)를 인자로 받습니다. 실행자 함수가 실행되면, 함수 내부에서는 비동기 작업이 이루어지고 만약 비동기 작업이 성공했을 시에는 그 성공 값을 인자로 resolve 함수를 호출하고, 만약 비동기 작업이 실패했을 시에는 그 실패 값을 인자로 reject 함수를 호출합니다.
const successPromise = new Promise(function (resolve, reject) {
  setTimeout(function () {
    resolve("Success");
  }, 3000);
}); // 비동기 작업 완료 후, 성공 값 "Success"를 가진 프로미스 객체(인스턴스)를 생성하고 변수에 할당

const failurePromise = new Promise(function (resolve, reject) {
  setTimeout(function () {
    reject(new Error("Request is failed"));
  }, 3000);
}); // 실패 값 new Error("Request is failed")를 가진 프로미스 객체(인스턴스)를 생성하고 변수에 할당

개발자 도구 콘솔 창에서 찍어보면 보다 명확히 알 수 있습니다.

해당 결괏값은 프로미스 객체의 내부 속성이기 때문에 직접 접근은 불가하고, 이후에 다루는 then, catch 메서드를 통해서만 접근이 가능합니다.

 

✔ Promise 기본  작성 코드 방법

const promise = new Promise((resolve, reject) => {
  /*
  비동기 작업 성공시 resolve()를 호출하고,
  비동기 작업 실패시 reject()를 호출하도록 구현.
  */
})

 

- Promise 다음엔 then()과 catch()를 사용합니다.

- then()은 생성한 프로미스 객체에서 인수로 전달한 resolve 가 호출되면 실행됩니다. then을 가지고 메서드 체이닝을 통하여서 콜백 헬 문제를 해결할 수 있습니다.

- catch()는 생성한 프로미스 객체에서 인수로 전달한 reject가 호출되면 실행됩니다.

 

const promise = new Promise((resolve, reject)=>{
  //처리 내용
})

promise.then(
  //resolve가 호출되면 then이 실행
)
.catch(
  //reject가 호출되면 catch가 실행
)
.finally(
  //콜백 작업을 마치고 무조건 실행되는 finally (생략 가능)
)

 

✔ Promise의 3가지 상태 (states)

  • 프로미스의 상태(states)란 프로미스의 처리 과정을 의미합니다.
  • new Promise()로 프로미스를 생성하고 종료될 때까지 3가지 상태를 갖습니다.
  • 비동기 처리가 완료되지 않았다면 Pending완료되었다면 Fulfilled실패하거나 오류가 발행했다면 Rejected 상태를 갖습니다.

1. Pending(대기) : 비동기 처리 로직이 아직 완료되지 않은 상태

// new Promise() 메서드를 호출하면 대기 상태가 됨.
new Promise()
 
// new Promise() 호출 시 콜백 함수를 선언할 수 있고, 인자는 resolve, reject 임
new Promise(function(resolve, reject) {
  //...
})

 

2. Fulfilled(이행) : 비동기 처리가 완료되어 프로미스가 결과 값을 반환해준 상태

// new Promise() 메서드를 호출하면 대기 상태가 됨.
new Promise()
 
// new Promise() 호출 시 콜백 함수를 선언할 수 있고, 인자는 resolve, reject 임
new Promise(function(resolve, reject) {
  //...
})

 

3. Rejected(실패) : 비동기 처리가 실패하거나 오류가 발생한 상태

  • 완료 상태인 Fulfilled와 Rejected를 합쳐 settled라고 합니다.
// 콜백 함수의 인자 reject를 실행하면 실패(Rejected) 상태가 된다.
new Promise(function(resolve, reject) {
  reject()
})
 
// 실패 상태가 되면 실패한 이유 (실패 처리의 결과 값)를 catch()로 받을 수 있다.
function getData() {
  return new Promise(function(resolve, reject) {
    reject(new Error("Request is failed"))
  })
}
getData().then().catch(function(err) {
  console.log(err) // 결과 : Error: Request is failed
})

 

✔ Promise  좀 더 심도 있게 알아보기

1. then

then 메서드는 프로미스가 이행(fulfilled)되었을 때 실행되는 함수이고 함수를 첫 번째 인자로 받는데, 그 함수의 인자는 Promise의 성공 결괏값을 받습니다.

const successPromise = new Promise(function (resolve, reject) {
  setTimeout(function () {
    resolve("Success");
  }, 3000);
});

successPromise.then(function (value) {
  console.log(value); // value인자가 결과 값 "Success"임.
});

successPromise.then((value) => console.log(value)); // 위와 동일한 코드

 

사실 then 메서드는 프로미스가 거부(reject)된 경우에도 두 번째 인자로 넣어진 함수를 통해 핸들링이 가능합니다.

const failurePromise = new Promise(function (resolve, reject) {
  setTimeout(function () {
    reject(new Error("Request is failed"));
  }, 3000);
});

failurePromise.then(
  function (value) {
    console.log(value);
  }, // 프로미스가 거부된 상태이기 때문에 첫번째 인자로 넣어진 함수는 실행되지 않음.
  function (err) {
    console.log(err);
  }
); // 프로미스가 거부된 상태이기 때문에 두번째 인자로 넣어진 함수만 실행됨.

failurePromise.then(
  (value) => console.log(value),
  (err) => console.log(err)
); // 위와 동일한 코드

 

위와 같이 then 메서드는 프로미스가 이행되거나 거부된 2가지 경우 모두 제어가 가능하지만, 통상적으로 then 메서드는 인수에 하나만 전달하여, 비동기 작업이 성공적으로 처리된 경우만 다루고 작업이 실패했을 경우는 그 결과 값을 catch메서드를 사용하여 제어를 합니다.

 

2. catch

catch 메서드는 프로미스가 거부(rejected)되었을 때 실행되는 함수이고 함수를 인자로 받는데, 그 함수의 인자는 여기서 거부 결과 값을 받습니다.

const failurePromise = new Promise(function (resolve, reject) {
  setTimeout(function () {
    reject(new Error("Request is failed"));
  }, 3000);
});

failurePromise
  .then(function (value) { // 거부(실패)된 프로미스는 then 메소드를 통과하고 
    console.log(value);
  })
  .catch(function (error) {
    console.log(error); 
  }); // catch메소드를 실행. error인자가 거부 결과 값임.

failurePromise
  .then((value) => console.log(value))
  .catch((error) => console.log(error)); // 위와 동일한 코드

 

3. Promise Chaining

then메서드와 catch메서드의 반환 값(return)은 또 다른 프로미스 객체를 반환하기 때문에, 서로 Chaining이 가능합니다.

const successPromise = new Promise((resolve, reject) => {
  setTimeout(function () {
    resolve("Success");
  }, 3000);
});

const anotherPromise = (value) => {
  return new Promise((resolve, reject) => {
    setTimeout(function () {
      resolve(`${value} not`);
    }, 1000);
  });
};

successPromise
  .then((value) => `${value} is`) //  `${value} is`를 결과 값으로 가진 Promise 객체 생성
  .then((secondValue) => anotherPromise(secondValue)) // 다른 프로미스가 처리될 때까지 기다리다가 처리가 완료되면 그 결과를 받음.
  .then((thirdValue) => console.log(thirdValue + " impossible"))
  .catch((error) => {
    errorHandling(error);
    return "again?"; // catch 메소드 이후에도 체이닝 가능.
  })
  .then((lastValue) => console.log(lastValue));

// 약 4초 후에 "Success is not impossible"을 출력 

 

여기서 catch메서드는 상위에 체이닝되어 있는 어떤 함수에서 에러가 나더라도 에러 핸들링이 가능합니다.

const successPromise = new Promise((resolve, reject) => {
  setTimeout(function () {
    resolve("Success");
  }, 3000);
});

successPromise
  .then((value) => `${value} is`)
  .then((secondValue) => {
    throw new Error("Error!!");
  }) // 에러 발생
  .then((thirdValue) => console.log("possible")) // 에러가 발생했으므로 통과함.
  .catch((error) => {
    console.log(error); 
  }); // 위 작업 어디에서든지 에러가 발생하면 catch 메소드가 실행됨.

 

4.finally

finally메서드는 Promise의 성공과 실패에 관계없이 처리만 되면 실행되는 함수입니다. 따라서 finally에선 프라미스가 성공되었는지, 실패되었는지 알 수 없습니다.

const successPromise = new Promise((resolve, reject) => {
  setTimeout(function () {
    resolve("Success");
  }, 3000);
});

successPromise
  .then((value) => `${value} is`)
  .then((secondValue) => {
    throw new Error("Error!!");
  }) // 에러 발생
  .then((thirdValue) => console.log("possible"))
  .catch((error) => {
    console.log(error);
  })
  .finally(() => console.log("chain end"));
// 위 Promise상태가 어떻든 간에 Promise 객체가 반환되었기 때문에 finally 메소드가 무조건적으로 실행 됨.

 

5.Promise.all

Promise.all메서드는 배열과 같이 순회 가능한 객체(주로 거의 배열이라고 한다)를 인자로 받습니다. 해당 배열 안의 프로미스가 모두 이행되면(배열 요소가 반드시 프로미스일 필요는 없다), 각각의 프로미스 결과 값을 담은 배열이행 결과 값으로 새로운 프로미스 객체를 반환합니다.

const one = new Promise((resolve, reject) => {
  setTimeout(() => resolve("one"), 1000);
});
const two = new Promise((resolve, reject) => {
  setTimeout(() => resolve("two"), 2000);
});
const three = new Promise((resolve, reject) => {
  setTimeout(() => resolve("three"), 3000);
});

Promise.all([one, two, three]).then((val) => console.log(val));
/* 배열 안 모든 프로미스가 이행된 후(약 3초 이후) 각 이행 결과값을 담은 배열을
   결과값으로 갖는 프로미스 객체가 만들어져
   콘솔에는 ["one", "two", "three"]가 출력됨.*/

Promise.all(["Hi", 123, three]).then((val) => console.log(val));
/* 배열 안 요소가 반드시 프로미스가 아닌 경우에도 가능함.
  하지만 이 경우에도 요소 안애 프로미스가 있다면 프로미스가 이행된 이후에 프로미스 객체가 생성됨.*/

 

5-1. 주의점

하지만 이때  배열 요소 중 하나의 프로미스가 거부되는 즉시, 다른 프로미스 이행 여부와 관계없이 해당 거부 사유를 결과 값으로 반환합니다.

 

const one = new Promise((resolve, reject) => {
  setTimeout(() => resolve("one"), 1000);
});
const two = new Promise((resolve, reject) => {
  setTimeout(() => resolve("two"), 2000);
});
const three = new Promise((resolve, reject) => {
  setTimeout(() => reject(new Error("Error!!")), 3000);
});

Promise.all([one, two, three])
  .then((val) => console.log(val))
  .catch((err) => console.log(err));
  // 다른 프로미스 이행 여부와 관계없이 catch 메소드가 호출

 

반환하는 프로미스의 이행 값은 매개변수로 주어진 프로미스의 순서와 일치하며, 완료 순서에 영향을 받지 않습니다.

const one = new Promise((resolve, reject) => {
  setTimeout(() => resolve("one"), 3000);
});
const two = new Promise((resolve, reject) => {
  setTimeout(() => resolve("two"), 2000);
});
const three = new Promise((resolve, reject) => {
  setTimeout(() => resolve("three"), 1000);
});

Promise.all([one, two, three]).then((val) => console.log(val));
// ["one", "two", "three"] 출력
// 배열의 첫번째 요소가 가장 마지막으로 이행 값을 반환했지만, 전달된 순서를 유지함.

 

위의 예시처럼 여러 가지 비동기 작업을 병렬적으로 실행하는 과정에서 비동기 작업이 시작된 순서를 유지해야 되는 경우라면 Promise.all을 활용하면 됩니다.

 

✔ Promise 처리의 흐름

 

✔ Promise의 에러 처리 방법

  • 실제 서비스를 구현하다 보면 네트워크 연결, 서버 문제 등으로 인해 오류가 발생할 수 있습니다.
  • 이때 에러 처리 방법은 2가지가 있는데, 모두 프로미스의 reject() 메서드가 호출되어 실패 상태가 된 경우에 실행됩니다.

then()의 두 번째 인자로 에러를 처리하는 방법

getData().then( 
  handleSuccess,
  handleError
 )

catch()를 이용하는 방법

- 프로미스의 에러 처리는 가급적 catch()를 이용하는 것이 효율적입니다.

getData().then().catch();

 

✔ Point of Promise

흔히 프로미스의 장점으로는 콜백 함수를 통한 비동기 처리 시 발생하는 콜백 헬을 해결하는 것으로만 초점이 맞추어져 있는데, 그보다 중요한 2가지 포인트가 있습니다.

The point of promises is to give us back functional composition and error bubbling in the async world. They do this by saying that your functions should return a promise, which can do one of two things:
- Become fulfilled by a value
- Become rejected with an exception
출처 - https://blog.domenic.me/youre-missing-the-point-of-promises/

즉 비동기 처리 방식에서

  • return value를 이용할 수 있다는 점
  • error handling이 동기식 코드와 유사하게 쓰일 수 있다는 점

 

👍 async & await란?

  • async & await는 비동기식 코드를 동기식으로 표현하여 간단하게 나타내는 것을 의미합니다.
  • ES8 추가로 도입된 async functions 그리고 await 키워드는 Promise 결과 값을 then, catch를 통해 다루는 것이 아닌
    변수의 담아 동기적 코드처럼 작성해줄 수 있다는 점에서 편리함을 제공합니다.
  • async & await는 Promise 객체를 반환하며 ⇒ then을 사용할 수 있습니다.

 

✔ async & await 기본 문법

async function 함수명() {
  await 비동기_처리_메서드_명()
}
  • async/await를 사용하기 위해서는 선행되어야 하는 조건이 있는데, await는 async 함수 안에서만 동작합니다.
  • 함수의 앞에 async라는 예약어를 붙입니다.
  • 함수의 내부 로직 중 HTTP 통신을 하는 비동기 처리 코드 앞에 await를 붙입니다.
    • 비동기 처리 메서드가 꼭 프로미스 객체를 반환해야 await 가 의도한 대로 동작합니다.
    • 일반적으로 await의 대상이 되는 비동기 처리 코드는 Axios 등 프로미스를 반환하는 API 호출 함수입니다.

1.async

먼저 비동기 함수를 async function으로 만들기 위하여 function() 앞에 async keyword를 추가합니다.
async function()은 await 키워드가 비동기 코드를 호출할 수 있게 해주는 함수입니다.
출처 - MDN
  • async는 function 앞에 위치합니다.
  • funciton 앞에 async를 붙이면 해당 함수는 항상 프로미스를 반환합니다.
    • 프로미스가 아닌 값을 반환하더라도 이행 상태의 프로미스(resolved promise)로 값을 감싸 이행된 프로미스가 반환되도록 합니다.
    •  async 가 붙은 함수는 반드시 프로미스를 반환하고, 프로미스가 아닌 것은 프로미스로 감싸 반환합니다.
  • async 함수는 화살표 함수로도 정의가 가능하고, 함수 표현식으로도 정의가 가능합니다.
  • async 함수를 실행하게 되면 무조건 Promise 객체가 반환되며 async 함수 내에서 return은 반환된 Promise 객체의 결과(resolve) 값입니다.

 

async function name() {
  return "chan"; // async function 내부의 return값은 Promise 객체의 결과값을 반환.
}

const foo = name(); // 변수 foo에 프로미스 객체가 할당.
console.log(foo); // Promise {<fulfilled>: "chan"}

 

2. await

  • await 키워드는 반드시 async함수 안에서만 사용할 수 있고, 일반 함수에서 사용하면 SyntaxError를 발생시킵니다.
  • 자바스크립트는 await 키워드를 만나면 해당 함수가 Promise 상태가 이행될 때까지 기다렸다가, 이행이 완료되면 그 결과 값을 반환하고 다음 코드를 실행합니다.
const promise = function () {
  return new Promise((resolve, reject) => {
    setTimeout(() => resolve("Done!!"), 2000);
  });
};
async function foo() {
  const result = await promise(); // 프라미스가 이행될 때까지 기다렸다가,
  console.log(result); // 완료 되면 하단의 코드가 이어서 실행됨
}

foo();

 

예제 코드로 이해하기

// async & await 예제
 
function delay(ms) {
  return new Promise(resolve => setTimeout(resolve, ms));
}
 
//2초동안 기다리게 하고 사과를 리턴하는 메서드
async function getApple() {
  await delay(2000)
  return '🍎'
}
 
//1초동안 기다라게 하고 사과를 리턴하는 메서드
async function getBanana() {
  await delay(1000)
  return '🍌'
}

getApple().then(console.log)  // 결과 : 🍎
getBanana().then(console.log) // 결과 : 🍌

 

await키워드를 사용하면 기존에 실행 순서가 예측이 불가능했던 비동기 작동 방식이 동기적으로 실행되는 코드처럼 예측 가능해질 수 있다는 점에서 장점을 드러냅니다.

console.log(1);

const promise = function () {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      console.log(3);
      resolve("two");
    }, 3000);
  });
};

const promiseTwo = function () {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve("one");
    }, 1000);
  });
};

console.log(2);

async function foo() {
  const result = await promise(); // 프라미스가 이행될 때까지 아래 코드로 넘어가지 않음..
  const resultTwo = await promiseTwo(); // 위 코드의 프로미스가 반환될 때까지 대기...

  console.log(resultTwo); // 완료 되면 하단의 코드가 이어서 실행됨

  const parellOne = promise(); // 위 아래 타이머는 동시에 시작됨.
  const parelltwo = promiseTwo(); // 해당 프로미스 이행 값이 먼저 반환됨.(약 1초)

  console.log(await parellOne);
  console.log(await parelltwo); // 먼저 프로미스 객체가 반환되었지만 위 함수가 먼저 실행되어야 실행됨.
}

foo(); // 콘솔에 찍히는 값은 순서대로 1 2 3 "one" 3 "two" "one"

 

✔ async & await 예외 처리

  • async & await에서 예외를 처리하는 방법은 try.. catch.. 구문을 사용하는 것입니다.
  • catch {}를 사용하면 됩니다. (Promise에서는. catch() 사용)
function fetchData() {
  return new Promise((resolve, reject) => {
      setTimeout(() => {
          return resolve('success')
      }, 1000)
  })
}
 
async function loadData() {
  try {
      const result = await fetchData()
      console.log(result)
  } catch(e) {
      console.log(e)    
  }
}
 
loadData()

 

try문에서 어떤 곳에서든지 에러가 발생하면 제어 흐름이 catch블록으로 넘어가고 이는 마찬가지로 동기식 코드에서 에러 핸들링을 하는 것도 유사하다는 점에서 또 장점을 발휘합니다.


출처 :

반응형
광고차단(애드블록)프로그램이나 브라우저를 사용중이시면 프로그램을 비활성화이나 블로그 주소를 예외 처리 해주시면 광고가 노출됩니다. 귀찮고 번거롭겠지만 광고 클릭은 저에게는 큰 힘이 됩니다. 🙇🏻‍♂️🙇🏻‍♀️
제 블로그의 모든 글은 제가 직접 작성 하고 다른 글을 참고할 때는 이전 글보다 읽기 편하게 수정해서 작성하고 있습니다. 커피 한잔 사먹고 더 열심히 좋은글로 보답하겠습니다.
오늘도 제 블로그에 와 주셔서 감사합니다. :)
//