현록

JavaScript some과 every 차이

JavaScript 배열이 조건을 만족하는지 확인할 때 some()every()를 사용할 수 있다.
두 메서드 모두 boolean을 반환하지만 통과해야 하는 요소의 수가 다르다.

하나라도 조건을 만족하면 되는 경우에는 some()을 사용한다.
모든 요소가 조건을 만족해야 하는 경우에는 every()를 사용한다.

some과 every의 반환 기준

some()은 콜백이 truthy를 반환하는 요소를 하나라도 찾으면 true를 반환한다.
목록에 특정 요소가 존재하는지, 하나 이상의 오류가 있는지 확인하는 조건에 맞는다.

every()는 방문한 모든 요소에서 콜백이 truthy를 반환해야 true를 반환한다.
모든 입력값이 유효한지, 전체 작업이 완료됐는지 확인하는 조건에 맞는다.

const users = [
  { id: 1, role: 'member', verified: true },
  { id: 2, role: 'admin', verified: true },
  { id: 3, role: 'member', verified: false },
]

const hasAdmin = users.some((user) => user.role === 'admin')
const allVerified = users.every((user) => user.verified)

console.log(hasAdmin) // true
console.log(allVerified) // false

두 메서드 모두 새 배열이나 요소가 아닌 boolean을 반환한다.

반환값과 단축 평가

some()은 첫 truthy 결과를 만나는 즉시 true를 반환하고 순회를 끝낸다.
every()는 첫 falsy 결과를 만나는 즉시 false를 반환하고 순회를 끝낸다.

const numbers = [2, 4, 7, 10]
let someCalls = 0
let everyCalls = 0

const hasLargeNumber = numbers.some((number) => {
  someCalls += 1
  return number > 5
})

const allSmallNumbers = numbers.every((number) => {
  everyCalls += 1
  return number < 5
})

console.log(hasLargeNumber, someCalls) // true, 3
console.log(allSmallNumbers, everyCalls) // false, 3

단축 평가는 불필요한 조건 검사를 줄여 준다.
반대로 콜백이 모든 요소에서 반드시 실행될 것이라고 기대하면 일부 작업이 누락된다.
모든 요소에 부수 효과를 실행하려는 목적이라면 some()이나 every()를 순회 도구로 사용하지 않는 편이 좋다.

콜백 반환값

콜백은 현재 요소, index, 순회 중인 배열을 인자로 받고 결과는 boolean으로 변환된다.
빈 문자열과 0은 falsy이고 객체와 Promise는 truthy다.
문자열 자체를 반환하는 코드도 동작하지만 field.error !== ''처럼 판정을 명시하면 의도를 더 쉽게 읽을 수 있다.

빈 배열의 결과

빈 배열에서 some()false, every()true를 반환한다.
콜백은 두 경우 모두 한 번도 실행되지 않는다.

console.log([].some(() => true)) // false
console.log([].every(() => false)) // true

빈 배열에는 조건을 만족하는 요소가 하나도 없으므로 some()의 결과는 false다.
반대로 조건을 위반하는 요소도 하나도 없으므로 모든 요소가 조건을 만족한다는 every()의 결과는 true다.

이 규칙 때문에 입력 목록이 비어 있어도 제출 조건이 통과할 수 있다.
요소가 하나 이상이어야 한다는 요구사항은 배열 길이로 따로 표현해야 한다.
예를 들어 requiredFields.length > 0 && requiredFields.every((field) => field.valid)처럼 길이 조건을 먼저 확인한다.
every()가 true라는 사실만으로 검사한 요소가 실제로 존재했다고 판단하면 안 된다.

부정 조건의 표현

일치하는 요소가 하나도 없어야 한다면 !items.some(isBlocked)처럼 some 결과를 부정할 수 있다.
모든 요소가 통과하지는 않는다는 조건은 !items.every(isReady)로 표현할 수 있다.
이중 부정이 이어진다면 isValid, hasInvalid처럼 조건에 이름을 붙이는 편이 낫다.

희소 배열의 빈 슬롯

some()every()는 값이 할당되지 않은 빈 슬롯에서 콜백을 호출하지 않는다.
length만 있고 실제 요소가 없는 희소 배열에서는 빈 배열과 같은 결과가 나올 수 있다.

const slots = new Array(3)

console.log(slots.some(() => true)) // false
console.log(slots.every(() => false)) // true

직접 넣은 undefined는 빈 슬롯과 다르며 콜백의 방문 대상이다.
희소 배열에 every()를 사용해 데이터가 모두 유효하다고 판단하면 누락된 index를 놓칠 수 있다.

두 메서드는 첫 콜백 전에 순회할 length 범위를 정하므로 도중에 추가한 요소는 방문하지 않는다.
아직 방문하지 않은 요소를 삭제하면 해당 index도 건너뛴다.
검사 중 같은 배열을 변경하지 않고 빈 슬롯이 없는 배열을 사용하는 편이 결과를 예측하기 쉽다.

비동기 콜백의 함정

some()every()는 async 콜백이 반환한 Promise를 기다리지 않는다.
Promise 객체 자체는 truthy이므로 실제 비동기 결과가 false여도 판정이 끝날 수 있다.

const users = [{ id: 1 }, { id: 2 }]

async function checkAccess() {
  return false
}

const hasAccess = users.some(async (user) => {
  return await checkAccess(user.id)
})

console.log(hasAccess) // true

배열이 비어 있지 않다면 첫 async 콜백이 반환한 Promise만으로 some()은 true가 된다.
같은 이유로 every()도 각 Promise를 truthy로 보고 실제 결과를 검사하지 않는다.

모든 검사를 병렬로 실행해도 된다면 Promise를 먼저 해결한 뒤 boolean 배열에 메서드를 적용한다.

const accessResults = await Promise.all(
  users.map((user) => checkAccess(user.id)),
)

const hasAccess = accessResults.some(Boolean)
const allHaveAccess = accessResults.every(Boolean)

첫 성공이나 실패에서 이후 비동기 요청을 시작하지 않아야 한다면 for...ofawait로 순차 검사한다.

다른 배열 메서드와의 선택 기준

조건을 만족하는 요소가 존재하는지만 필요하면 some()을 사용한다.
모든 요소가 조건을 만족하는지 필요하면 every()를 사용한다.

첫 요소 자체가 필요하면 find(), 조건을 통과한 요소 목록이 필요하면 filter()가 맞다.
반환 형태에 따른 차이는 JavaScript find와 filter 차이에서 자세히 볼 수 있다.

모든 요소를 변환한 새 배열이 필요하면 map(), 모든 요소에 작업을 실행하고 반환값이 필요하지 않으면 forEach()를 검토한다.
두 메서드의 기준은 JavaScript map과 forEach 차이에서 이어서 확인할 수 있다.

특정 값 하나의 포함 여부만 확인할 때는 predicate를 만들지 않고 includes()로 의도를 드러낼 수 있다.
필요한 결과가 boolean인지, 요소인지, 새 배열인지부터 정하면 배열 메서드 선택이 단순해진다.

참고 자료

관련 포스트
TypeScript any와 unknown 차이 thumbnail
TypeScript any와 unknown 차이
TypeScript any와 unknown의 차이를 타입 검사, 할당, 타입 좁히기, 외부 데이터 경계, 컴파일러 설정으로 정리합니다.
JavaScript 얕은 복사와 깊은 복사 차이 thumbnail
JavaScript 얕은 복사와 깊은 복사 차이
JavaScript 얕은 복사와 깊은 복사의 차이를 객체 참조, spread, structuredClone, JSON 복사의 제약으로 정리합니다.
JavaScript find와 filter 차이 thumbnail
JavaScript find와 filter 차이
JavaScript find와 filter의 차이를 반환값, 순회 종료, 객체 참조, 일치 항목이 없을 때의 처리 기준으로 정리합니다.
JavaScript null과 undefined 차이 thumbnail
JavaScript null과 undefined 차이
JavaScript null과 undefined의 차이를 값이 생기는 경로, 비교, nullish 처리, JSON 직렬화, API 선택 기준으로 정리합니다.
TypeScript type과 interface 차이 thumbnail
TypeScript type과 interface 차이
TypeScript의 type과 interface 차이를 객체 모델링, 유니온, 선언 병합, extends와 교차 타입의 충돌 처리, 실무 선택 기준으로 나누어 정리합니다.
JavaScript ==와 === 차이 thumbnail
JavaScript ==와 === 차이
JavaScript ==와 ===의 차이를 암시적 타입 변환, null과 undefined, boolean과 문자열, 객체 참조, NaN, 비교 연산자 선택 기준으로 정리합니다.
marked.js 사용법과 기본 설정 thumbnail
marked.js 사용법과 기본 설정
marked.js 사용법을 초보자 기준으로 정리합니다. marked.parse와 parseInline, gfm과 breaks 옵션, 브라우저 출력, HTML sanitize 주의점까지 함께 봅니다.
JavaScript var let const 차이 thumbnail
JavaScript var let const 차이
JavaScript var, let, const 차이를 함수와 블록 스코프, 재할당과 재선언, 호이스팅과 TDZ, 반복문 closure, 객체 변경 기준으로 정리합니다.
URL path parameter와 query string 차이 thumbnail
URL path parameter와 query string 차이
URL path parameter와 query string의 차이를 초보자 기준으로 정리합니다. 리소스 식별자, 조회 조건, URLSearchParams, 인코딩, API 설계 기준을 함께 봅니다.
쿠키와 localStorage 차이 thumbnail
쿠키와 localStorage 차이
쿠키와 localStorage의 차이를 초보자 기준으로 정리합니다. 서버 자동 전송, JavaScript 접근, 만료 시간, HttpOnly, Secure, SameSite, 로그인 상태 저장 기준을 함께 봅니다.
script async와 defer 차이 thumbnail
script async와 defer 차이
HTML script 태그의 기본 동작, async와 defer의 다운로드 방식, 실행 순서, DOMContentLoaded와의 관계를 초보자 기준으로 정리합니다.
CORS 에러와 해결 기준 thumbnail
CORS 에러와 해결 기준
브라우저의 Same-Origin Policy, CORS 응답 헤더, preflight, 서버에서 해결해야 하는 이유를 초보자 기준으로 정리합니다.
fetch와 axios 차이 thumbnail
fetch와 axios 차이
fetch와 axios의 차이를 초보자 기준으로 정리합니다. 설치 여부, JSON 처리, HTTP 에러 처리, timeout, interceptor, 언제 어떤 방식을 쓰면 좋은지 함께 봅니다.
JavaScript map과 forEach 차이 thumbnail
JavaScript map과 forEach 차이
JavaScript의 map과 forEach 차이를 반환값, 새 배열, 부수 효과, 원본 변경, 비동기 콜백, Promise.all 사용 기준으로 나누어 정리합니다.
Vue 3 입문기 thumbnail
Vue 3 입문기
Vue의 메인 버전이 3가 된지 꽤 됐다. 작성 당시에는 Nuxt 3가 RC 단계였기 때문에 실서비스에 바로 적용하기에는 조심스러웠다. 그래서 궁금하고 심심하던 참에 간단한 Todo App을 만들어봤다.
marked.js renderer custom 하기 thumbnail
marked.js renderer custom 하기
marked.js renderer custom 방법을 정리합니다. Renderer의 역할, image와 link 렌더러 커스텀, alt와 rel 처리, sanitize-html 같은 HTML 정화 주의점까지 함께 봅니다.
가독성 있게 상수 넣기 thumbnail
가독성 있게 상수 넣기
오늘 회사 동료의 PR 리뷰 과정에서 좋은 기능을 공유해주셔서 TIL로 남겨봅니다. 아래와 같이 상수에 언더바(_)로 콤마처럼 구분을 시켜줄 수 있습니다. 앞으로 깔끔하고 좋은 코드를 작성하기 위해 자주 사용해야겠습니다 :)
TS에서 generic optional 하게 설정하기 thumbnail
TS에서 generic optional 하게 설정하기
오늘 next에서 `getStaticProps`와 `getLayout` 패턴을 함께 사용할 때, typescript generic을 넘겨주는 작업을 하고 있었는데, 기본값이 없다보니 기존 코드에 에러가 발생했었다. 이를 해결하기 위해 찾아보니 단순히 아래 예시처럼 `= {}`을 추가해주면 해결된다고 한다.
Backend에서 API Response가 snake_case인 경우엔? thumbnail
Backend에서 API Response가 snake_case인 경우엔?
안녕하세요. 프론트엔드 개발자의 경우, 가끔 백엔드의 API Response 값이 snake_case일 경우 어떻게 관리할지에 대해 고민에 빠지게 됩니다. 저도 오늘 같은 상황을 겪게 되었는데,  이번엔 네이밍 컨벤션을 맞춰주기로 했습니다. 컨벤션을 맞추는 데에는 여러가지 방법이 있겠지만, 고민 끝에 저는 axios의 interceptors를 통해 해결을 해보았습니다.
Promise 다루기 (feat. 병렬실행, 순차실행) thumbnail
Promise 다루기 (feat. 병렬실행, 순차실행)
오늘은 Promise를 통해 구문을 동기 처리 할 때, 여러 Promise들을 다루는 법을 소개해보겠습니다. Javscript를 작성하다 보면, 가끔 여러 Promise들을 다룰 때가 있습니다. 필자도 Nodejs 서버에서 동시에 여러 쿼리를 실행할 때 자주 마주쳤었는데요. 오늘은 어떻게 하면 Promise들을 유연하게 다룰 수 있는지 알아보겠습니다. 시작하기 앞서, 네 가지 Promise를 선언하고, 그들을 하나의 Array에 묶어보겠습니다.