λ³Έλ¬Έ λ°”λ‘œκ°€κΈ°
πŸ‘¨‍πŸ’» web.dev/js.ts

async function μ‹λ³„ν•˜κΈ°

by HandHand 2022. 8. 8.

 

πŸ“Œ 문제 상황

사내 ν”„λ‘œμ νŠΈλ₯Ό μ§„ν–‰ν•˜λ‹€κ°€ prop 으둜 전달받은 ν•¨μˆ˜μ˜ λ°˜ν™˜κ°’μ— λ”°λΌμ„œ

κ·Έ λ‹€μŒ ν˜ΈμΆœλ˜λŠ” μ•‘μ…˜ ν•Έλ“€λŸ¬λ₯Ό μ œμ–΄ν•˜κΈ° μœ„ν•œ κ²½μš°κ°€ μƒκ²ΌμŠ΅λ‹ˆλ‹€.

μ•„λž˜λŠ” 이λ₯Ό κ°„λž΅ν™”ν•œ μ˜ˆμ‹œ μ½”λ“œμž…λ‹ˆλ‹€.

 

interface ComponentProps {
    onAction: () => void
    onClick: () => unknown
}

export default function Component({ onClose }: ComponentProps) {
    // ...

    const handleClick = () => {
        onClick ? onClick() && onAction() : onAction()
    }

    return (
        <button onClick={handleClick}>try me!</button>
    )
}

 

μ—¬κΈ°μ„œ onClick 이 동기 ν•¨μˆ˜λΌλ©΄ μ•„λ¬΄λŸ° λ¬Έμ œκ°€ λ°œμƒν•˜μ§€ μ•ŠλŠ”λ°..

Promise λ₯Ό λ°˜ν™˜ν•  경우 onClick() 의 κ²°κ³Όκ°€ 항상 truthy ν•œ λ¬Έμ œκ°€ λ°œμƒν–ˆμŠ΅λ‹ˆλ‹€.

 

πŸ“Œ μ–΄λ–»κ²Œ ν•΄κ²°ν•˜μ§€…? πŸ€”

1️⃣ async / await 을 μ‚¬μš©ν•˜κΈ°

첫번째 방법은 μœ„ μ½”λ“œμ—μ„œ handleClick 을 async ν•¨μˆ˜λ‘œ λ°”κΎΈκ³ 

onClick 의 결과값을 await ν•œ 뒀에 κ·Έ λ°˜ν™˜κ°’μ„ μ΄μš©ν•˜λŠ” κ²ƒμž…λ‹ˆλ‹€.

Promise κ°μ²΄λŠ” λ°˜ν™˜κ°’μ΄ Promise κ°€ μ•„λ‹ˆλΌλ©΄ μ•Œμ•„μ„œ resolved promise 둜 λ³€ν™˜ν•˜κΈ° λ•Œλ¬Έμž…λ‹ˆλ‹€.

 

2️⃣ 비동기 ν•¨μˆ˜ μ‹λ³„ν•˜κΈ°

λ‘λ²ˆμ§Έ 방법은 onClick 이 비동기 ν•¨μˆ˜μΈμ§€ νŒλ‹¨ν•˜κ³ 

λ§Œμ•½ 비동기 ν•¨μˆ˜λΌλ©΄ Promise κ°€ pending 이 끝날 λ•ŒκΉŒμ§€ κΈ°λ‹€λ Έλ‹€κ°€ 이 응닡값을 ν†΅ν•΄μ„œ

λ‹€μŒ μ•‘μ…˜μ„ μ‹€ν–‰ν•  지 νŒλ‹¨ν•˜λŠ” λ‘œμ§μ„ λ„£λŠ” κ²ƒμž…λ‹ˆλ‹€.

첫번째 방법이 κ°„λ‹¨ν•˜κ³  κΉ”λ”ν•˜μ§€λ§Œ, 이번 κΈ°νšŒμ— λ‘λ²ˆμ§Έ 방법을 μ΄μš©ν•΄μ„œ

μ‹€μ œλ‘œ ν•΄λ‹Ή ν•¨μˆ˜κ°€ async function 인지 νŒλ‹¨ν•˜λŠ” 방법을 μ•Œμ•„λ³΄κ³  μ‹Άμ—ˆμŠ΅λ‹ˆλ‹€.

이λ₯Ό 톡해 μƒˆλ‘­κ²Œ μ•Œ 수 μžˆλŠ” 것듀도 μžˆλ‹€κ³  μƒκ°ν–ˆκΈ° λ•Œλ¬Έμ΄μ£ .

 

πŸ“Œ Function constructor 둜 async ν•¨μˆ˜ μ‹λ³„ν•˜κΈ°

1️⃣ Function & Async Function

JavaScript 의 λͺ¨λ“  function 은 Function 객체이며

async function 은 AsyncFunction μž…λ‹ˆλ‹€.

ECMAScript λͺ…세에 λ”°λ₯΄λ©΄ AsyncFunction 은 Function 의 ν•˜μœ„ 클래슀라고 ν•˜λ„€μš”.

 

2️⃣ Function name 으둜 ν•¨μˆ˜ μ‹λ³„ν•˜κΈ°

각각의 μƒμ„±μž ν•¨μˆ˜λŠ” name μ΄λΌλŠ” 속성을 κ°€μ§€λŠ”λ°,

이λ₯Ό ν†΅ν•΄μ„œ ν•΄λ‹Ή ν•¨μˆ˜μ˜ μ’…λ₯˜λ₯Ό 식별할 수 μžˆμŠ΅λ‹ˆλ‹€.

 

const f = () => {}
const asyncF = async () => {}

console.log(f.constructor.name) // βœ… Function
console.log(asyncF.constructor.name) // βœ… AsyncFunction

 

그러면 λ‹€μŒκ³Ό 같은 μ½”λ“œλ₯Ό μž‘μ„±ν•  수 μžˆκ² λ„€μš”!

 

function isAsyncFunction(func) {
    return func.constructor.name === 'AsyncFunction'
}

 

그런데 μœ„ μ½”λ“œλŠ” async ν‚€μ›Œλ“œλ‘œ μž‘μ„±λ˜μ§€ μ•Šμ€ ν•¨μˆ˜κ°€ Promise λ₯Ό λ°˜ν™˜ν•  λ•Œ λ¬Έμ œκ°€ μƒκΉλ‹ˆλ‹€.

λ‹€μŒμ€ μ΄λŸ¬ν•œ κ²½μš°μ— λŒ€ν•œ 간단 μ˜ˆμ‹œ μ½”λ“œμž…λ‹ˆλ‹€.

 

const f = () => { return Promise.resolve('is AsyncFunction?') }

console.log(f.constructor.name) // 🚨 Function

 

ν•¨μˆ˜ f λŠ” λΆ„λͺ… Promise λ₯Ό λ°˜ν™˜ν•˜κ³  μžˆμ§€λ§Œ, async ν‚€μ›Œλ“œλ‘œ μ„ μ–Έλ˜μ§€ μ•Šμ•˜κΈ°μ—

name 속성이 Function 이 λ˜λŠ” κ²ƒμž…λ‹ˆλ‹€.

 

πŸ“Œ ν•¨μˆ˜μ˜ λ°˜ν™˜κ°’μ΄ Promise 인지 μ‹λ³„ν•˜κΈ°

πŸ’‘ instanceof μ—°μ‚°μž ν™œμš©ν•˜κΈ°

λ”°λΌμ„œ 이 κ²½μš°κΉŒμ§€ μ²˜λ¦¬ν•  수 μžˆλ„λ‘ λ°˜ν™˜κ°’μ΄ Promise 인지 νŒλ‹¨ν•˜λŠ” λ‘œμ§μ„ μΆ”κ°€ν•©λ‹ˆλ‹€.

νŠΉμ • 값이 Promise μΈμ§€λŠ” λ‹€μŒκ³Ό 같이 instanceof μ—°μ‚°μžλ₯Ό 톡해 μ•Œ 수 μžˆμŠ΅λ‹ˆλ‹€.

이 μ—°μ‚°μžλŠ” μƒμ„±μžμ˜ prototype 속성이 νŠΉμ • 객체의 ν”„λ‘œν† νƒ€μž… 체이닝에 μ‘΄μž¬ν•˜λŠ”μ§€ μ²΄ν¬ν•©λ‹ˆλ‹€.

 

function asyncCallback () {
    return Promise.resolve(1)
}

const response = asyncCallback()

console.log(response instanceof Promise) // true 

 

ν•œκ°€μ§€ μœ μ˜ν•  점은 instanceof μ—°μ‚°μžκ°€ ν”Όμ—°μ‚°μžλ‘œ 객체와 μƒμ„±μžν•¨μˆ˜λ₯Ό λ°›λŠ”λ‹€λŠ” κ²ƒμž…λ‹ˆλ‹€.

λ”°λΌμ„œ instanceof λ₯Ό μ‚¬μš©ν•˜κΈ° 전에 λ¨Όμ € μΈμžκ°’μ΄ 객체인지도 확인이 ν•„μš”ν•©λ‹ˆλ‹€.

이λ₯Ό μœ„ν•΄ λ‹€μŒκ³Ό 같이 isObject μœ ν‹Έλ¦¬ν‹° ν•¨μˆ˜λ₯Ό κ΅¬ν˜„ν•΄μ€λ‹ˆλ‹€.

 

function isObject(obj) {
    return obj !== null && typeof obj === 'object'
}

 

μœ„ 두 ν•¨μˆ˜λ₯Ό μ‘°ν•©ν•˜λ©΄ λ‹€μŒκ³Ό 같이 Promise 객체인지 νŒλ‹¨ν•˜λŠ” ν•¨μˆ˜λ₯Ό κ΅¬ν˜„ν•  수 μžˆκ² λ„€μš”!

 

function isObject(obj: unknown): obj is Object {
    return obj !== null && typeof obj === 'object'
}

function isPromise(value: unknown): value is Promise<unknown> {
    return isObject(value) && value instanceof Promise
}

 

πŸ’‘ Native Promise κ°€ κ΅¬ν˜„μ΄ μ•ˆλœ ν™˜κ²½ (IE) κ³ λ €ν•˜κΈ°

λ‹€λ§Œ μœ„ 방법은 ES6 μ΄μƒμ˜ ν™˜κ²½μ—μ„œ Promise 객체λ₯Ό μ˜¨μ „νžˆ μ§€μ›ν•˜λŠ” κ²½μš°μ—λ§Œ μ‚¬μš©ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

λ§Œμ•½ IE 와 같이 ES5 κ°€ ν•„μš”ν•œ ν™˜κ²½μ—μ„œλŠ” Promise λ₯Ό μ‚¬μš©ν•˜κΈ° μœ„ν•΄μ„œ

bluebird λ‚˜ when.js 와 같은 라이브러리λ₯Ό μ‚¬μš©ν•  μˆ˜λ„ μžˆλŠ”λ°,

μ΄λŸ¬ν•œ κ²½μš°μ—λ„ ν˜Έν™˜ κ°€λŠ₯ν•˜κΈ° μœ„ν•΄μ„œ thenable 체크λ₯Ό μ‚¬μš©ν•˜λŠ” 방법도 μžˆμŠ΅λ‹ˆλ‹€.

μ΄λŠ” νŠΉμ • 객체에 then κ³Ό catch ν•¨μˆ˜κ°€ κ΅¬ν˜„λ˜μ–΄μžˆλŠ”μ§€ ν™•μΈν•˜λŠ” 방법을 μ΄μš©ν•©λ‹ˆλ‹€.

 

function isPromise(value: any): value is Promise<any> {
  return (
    value &&
    typeof value.then === 'function' &&
    typeof value.catch === 'function'
  )
}

 

πŸ“Œ 이제 μœ„ ν•¨μˆ˜λ“€μ„ μ‘°ν•©ν•©λ‹ˆλ‹€

 

μœ„μ—μ„œ μ •μ˜ν•œ ν•¨μˆ˜λ“€μ„ λͺ¨λ‘ ν•©μ³μ„œ λ‹€μŒκ³Ό 같이 νŠΉμ • ν•¨μˆ˜κ°€ 비동기 값을 λ°˜ν™˜ν•˜λŠ”μ§€ νŒλ‹¨ κ°€λŠ₯ν•©λ‹ˆλ‹€.

ν•¨μˆ˜μ˜ λ°˜ν™˜κ°’μ΄ Promise 인지 μ•ŒκΈ° μœ„ν•΄μ„œλŠ” λ¨Όμ € ν•¨μˆ˜λ₯Ό 싀행해봐야겠죠?

인자둜 받은 function 이 μš°μ„  callable ν•œμ§€ νŒλ‹¨ν•˜κΈ° μœ„ν•œ 확인이 ν•„μš”ν•˜λ©°

이λ₯Ό μœ„ν•œ isFunction ν•¨μˆ˜λ₯Ό κ΅¬ν˜„ν•΄μ€λ‹ˆλ‹€.

 

function isFunction(value: unknown): value is Function {
    return typeof value === 'function'
}

 

λ§ˆμ§€λ§‰μœΌλ‘œ μœ„ ν•¨μˆ˜λ“€μ„ μ‘°ν•©ν•˜μ—¬ λ‹€μŒκ³Ό 같이 비동기 ν•¨μˆ˜ νŒλ‹¨ μœ ν‹Έλ¦¬ν‹° ν•¨μˆ˜λ₯Ό κ΅¬ν˜„ν•©λ‹ˆλ‹€.

 

function isAsyncFunction(func: unknown) {
    if (!isFunction(func)) {
        return false
    }

    const functionType = func.constructor.name
    if ( 
      functionType === 'AsyncFunction' ||
      functionType === 'Function' &&  isPromise(func())
    ) {
        return true
    }

    return false
}

 

μ—¬κΈ°μ„œ func() λ₯Ό ν˜ΈμΆœν•˜μ—¬ λ°˜ν™˜κ°’μ΄ Promise 객체인지 ν™•μΈν•˜κ³  μžˆμŠ΅λ‹ˆλ‹€.

λΉ„μš©μ΄ μž‘μ€ ν•¨μˆ˜λΌλ©΄ 상관 μ—†κ² μ§€λ§Œ, μ‹œκ°„μ΄ 였래 κ±Έλ¦¬λŠ” API 호좜 λ“±

μ—¬λŸ¬ 비동기 μž‘μ—…μ΄ μ–½ν˜€μžˆλŠ” 경우라면 μ–˜κΈ°κ°€ 달라지겠죠?

κ·Έλž˜μ„œ ν”„λ‘œμ νŠΈμ—μ„œλŠ” AsyncFunction μœ λ¬΄μ™€ ν•¨μˆ˜μ˜ λ°˜ν™˜κ°’λ„ ν•¨κ»˜ μ „λ‹¬ν•˜λ„λ‘ ν–ˆμŠ΅λ‹ˆλ‹€ πŸ˜€

μœ„μ—μ„œ κ΅¬ν˜„ν•œ ν•¨μˆ˜λ₯Ό 확인해보기 μœ„ν•΄ ν™œμš©ν•œ ν…ŒμŠ€νŠΈ μΌ€μ΄μŠ€λŠ” λ‹€μŒκ³Ό κ°™μŠ΅λ‹ˆλ‹€.

 

const testFuncExpression = () => { return 1 }
const testAsyncFuncExpression = async () => { return 1 }
function testFuncDeclaration() { return 1 }
async function testAsyncFuncDeclaration() { return 1 }
const asyncCallbackFunc = () => { return Promise.resolve(1) }

console.log(isAsyncFunction(testFuncExpression)) // false
console.log(isAsyncFunction(testAsyncFuncExpression)) // true
console.log(isAsyncFunction(testFuncDeclaration)) // false
console.log(isAsyncFunction(testAsyncFuncDeclaration)) // true
console.log(isAsyncFunction(asyncCallbackFunc)) // true

const primitives = [1, true, null, undefined, Symbol(1), 'func']

for (const primitive of primitives) {
    console.log(isAsyncFunction(primitive)) // false * 6
}

 

πŸ“Œ 참고자료

How to Check if a Function is Async in JavaScript | bobbyhadz

Function - JavaScript | MDN

AsyncFunction - JavaScript | MDN

How to check if a variable is an object in JavaScript

How to check if an object is a Promise?

 

λ°˜μ‘ν˜•

πŸ’¬ λŒ“κΈ€