location.href 는 암묵적 await 가 아니다 — setter 는 동기, navigation 은 태스크
location.href에 값을 넣으면 그 줄에서 멈추고 이동할 것 같다 — 암묵적 await 처럼. 그럼 뒤의 코드는 안 돌까. 추측하지 말고 CDP로 깨 본다.
1. 실험을 건다
외부 도메인 대신 로컬 서버의 /a·/b로 이동시킨다(서버는 받은 요청을 기록한다). Runtime·Page·Network를 enable 하고, 마지막 식의 값까지 돌려받게 한 줄을 붙여 Runtime.evaluate로 실행한다.
location.href = '/a'
console.log('실행됨 1')
location.href = '/b'
console.log('실행됨 2')
// 'evaluate 끝까지 돌았다'
await였다면 첫 줄에서 멈춰 실행됨 1도 안 찍히고 /a로 가야 한다.
2. 이벤트 순서를 읽는다
CDP가 받은 순서 그대로다.1
| ms | 이벤트 | |
|---|---|---|
| 514 | Page.frameRequestedNavigation |
/a · scriptInitiated |
| 516 | Runtime.consoleAPICalled |
실행됨 1 |
| 517 | Page.frameRequestedNavigation |
/b |
| 517 | Runtime.consoleAPICalled |
실행됨 2 |
| 517 | Runtime.evaluate 응답 |
'evaluate 끝까지 돌았다' |
| 518 | Page.frameStartedNavigating |
/a |
| 520 | Network.requestWillBeSent |
/a |
| 521 | Network.loadingFailed |
net::ERR_ABORTED · canceled: true |
| 523 | Page.frameStartedNavigating · requestWillBeSent |
/b |
| 528 | Page.frameNavigated |
/b |
- setter는 동기로 끝나고 흐름을 안 끊는다. 이동 요청(
frameRequestedNavigation)이 console 로그 사이사이에 찍히고,evaluate가 마지막 식의 값을 돌려준다 — 코드는 끝까지 돌았다. - 실제 이동은 그 뒤에 시작된다.
frameStartedNavigating은evaluate가 끝난 다음이다. 문서 언로드·페치는 태스크로 넘어가 지금 코드가 끝난 뒤에 돈다. - 마지막 값이 이기는 건 앞의 것이 취소되기 때문이다.
/a가 시작됐다가/b가 시작되며ERR_ABORTED로 끊긴다.2 덮어쓴 게 아니다.
3. 덤 — 버려진 이동도 서버에는 갔다
서버 기록은 ["/a","/b"]다. /a는 requestWillBeSent까지 갔다가 클라이언트에서 끊겼을 뿐, 요청은 서버에 도착했다. 이동 전에 서버 부수효과가 있는 URL(로그아웃 링크 같은)이라면 “결국 /b로 갔으니 /a는 없던 일”이 아니다 — 572의 “취소는 클라이언트에서만 일어난다”와 같은 이야기다.