Lesson 1.5.2 — Nested Calls
0. Metadata (Thông tin bài học)
| Thuộc tính | Giá trị |
|---|---|
| Stage | 1 — JavaScript Execution Model |
| Module | 1.5 — Call Stack & Execution Tracing |
| Lesson | 1.5.2 — Nested Calls |
| Competency | C02 — JavaScript Runtime (C02.6 Call Stack) |
| Depth Target | L3–L4 (Use / Debug) |
| Prerequisites | Function call & return (Stage 0), Execution Context (Module 1.1), Stack Frames (Lesson 1.5.1) |
| Cognitive Load | Medium |
1. Why This Exists (Vì sao cần học)
Bạn đọc đoạn code này:
function a() {
b();
}
function b() {
c();
}
function c() {
return 42;
}
console.log(a());c() rõ ràng return 42. Nhưng chương trình lại in: undefined
Nếu chỉ nhìn từng function riêng lẻ, kết quả này khá khó chịu. Muốn giải thích chính xác, bạn phải theo dõi đồng thời hai luồng:
- Control flow — function nào đang chạy, function nào đang chờ, khi nào quyền thực thi quay lại caller.
- Value flow — mỗi call expression tạo ra giá trị gì, giá trị đó được dùng,
return, hay bị bỏ qua.
Learning Goal (Mục tiêu học)
Lesson 1.5.1 cho bạn biết frame nào đang nằm trên Call Stack. Lesson này nâng mental model thêm một bước: khi các function gọi lồng nhau, control và return value di chuyển qua chuỗi caller → callee → caller như thế nào.
Đây là kỹ năng nền để sau này đọc stack trace, debug wrapper function, hiểu recursion, exception unwinding và async execution.
2. Prerequisites (Yêu cầu đầu vào)
Trước khi học bài này, bạn cần:
- [ ] Biết một function call tạo ra một lần thực thi mới.
- [ ] Biết
return expressionkết thúc lần gọi hiện tại và tạo ra kết quả cho call expression. - [ ] Vẽ được Call Stack cơ bản theo mô hình
push → execute → pop. - [ ] Xác định được frame trên cùng là function đang thực thi trong trace model của curriculum.
Nếu chưa vẽ chắc được chuỗi Global → a → b → c, quay lại Lesson 1.5.1 — Stack Frames trước.
3. Learning Objectives (Mục tiêu học tập)
Sau bài này, bạn có thể:
- Trace một chuỗi nested calls 3–4 tầng theo đúng thứ tự call, return và resume.
- Dự đoán output của code có nested calls mà không chạy chương trình.
- Phân biệt control flow với return-value flow.
- Giải thích tại sao một callee có thể trả giá trị đúng nhưng caller cuối cùng vẫn nhận
undefined. - Debug lỗi thiếu
returntrong một chuỗi wrapper/caller đơn giản. - Phân biệt lexical nesting trong source code với invocation nesting trên Call Stack.
4. Mental Model (Mô hình tư duy)
Hãy giữ mental model sau trong đầu:
Caller đang chạy
↓ call
Callee được push và chạy
↓ return value
Callee kết thúc
↓
Caller tiếp tục ngay sau call siteVới nhiều tầng:
CALL:
a()
↓
b()
↓
c()
RETURN:
c() → b() → a()Nhưng có một chi tiết quan trọng:
return value
≠
tự động đi xuyên qua mọi callerMỗi function phải chủ động return giá trị nếu muốn caller phía trên nhận được nó.
Mental Model (Mô hình tư duy)
Trong trace model của curriculum, khi a() gọi b(), frame của a() vẫn còn active ở bên dưới và tạm chờ b() hoàn tất. Khi b() return, quyền thực thi quay lại đúng call site trong a(). Đây là mental model để dự đoán behavior; bài này không đi vào memory layout hay implementation nội bộ của engine.
Hai câu hỏi phải luôn hỏi khi trace
Tại mỗi function call, hỏi:
- Control: Function nào được gọi tiếp? Caller nào đang chờ?
- Value: Call expression này tạo ra giá trị gì? Giá trị đó được
return, gán, dùng trong expression, hay bị bỏ qua?
Nếu trả lời đủ hai câu này, nested calls trở nên cơ học thay vì "đoán bằng cảm giác".
5. Core Concepts (Các khái niệm cốt lõi)
Essential (Bắt buộc)
| Khái niệm | Ý nghĩa trong lesson |
|---|---|
| Caller | Function đang thực hiện lời gọi đến function khác. |
| Callee | Function được caller gọi. |
| Call site | Vị trí expression nơi lời gọi function xảy ra. |
| Nested call | Một function call xảy ra trong khi một function caller khác vẫn chưa hoàn tất. |
| Resume | Caller tiếp tục thực thi sau khi callee hoàn tất. |
| Return-value propagation | Caller chủ động return kết quả của callee để chuyển giá trị lên tầng phía trên. |
Implicit undefined | Function đi đến cuối body mà không thực thi return value sẽ cho call expression kết quả undefined. |
Supporting (Hỗ trợ)
- Caller frame vẫn nằm dưới callee frame trong trace model khi callee đang chạy.
- Một call expression phải hoàn tất để expression bao quanh nó có được giá trị cần thiết.
return callee()vừa gọi callee, vừa dùng kết quả của call expression làm kết quả trả về của caller.callee();chỉ gọi callee; nếu kết quả không được dùng ở đâu, giá trị đó bị bỏ qua.
Awareness (Biết tồn tại)
- Stack trace trong DevTools thường phản ánh chuỗi caller đang active tại thời điểm lỗi.
- Sau này recursion tạo ra nhiều invocation của cùng một function trên stack.
- Exception có thể làm nhiều frame kết thúc mà không đi theo return flow bình thường.
Out of Scope (Không học trong bài này)
- Recursion và stack growth — Lesson 1.5.3.
- Exception propagation / stack unwinding — Lesson 1.5.4.
- Full trace kết hợp scope + closure + exception — Lesson 1.5.5.
- Promise, task, microtask và Event Loop — Stage 3.
- Engine-specific stack frame layout, JIT, machine stack.
6. Worked Example (Ví dụ phân tích từng bước)
Ta dùng đúng đoạn code trung tâm của Stage 1:
function a() {
b();
}
function b() {
c();
}
function c() {
return 42;
}
console.log(a());Step 1 — Global cần tính argument cho console.log
Để gọi: console.log(a());
JavaScript phải có giá trị của a() trước.
Trong source-level trace của lesson này, ta tập trung vào các function do chúng ta định nghĩa:
Call Stack:
[ Global ]Global bắt đầu evaluate a().
Step 2 — a() được gọi
Call Stack:
[ Global ]
[ a() ]a() chạy đến b();
a() chưa kết thúc. Nó đang chờ lời gọi b() hoàn tất.
Step 3 — b() được gọi
Call Stack:
[ Global ]
[ a() ]
[ b() ]b() chạy đến c();
Bây giờ cả a() và b() đều chưa hoàn tất.
Step 4 — c() được gọi
Call Stack:
[ Global ]
[ a() ]
[ b() ]
[ c() ]c() thực thi: return 42;
Call expression c() tạo ra giá trị: 42
Step 5 — Control quay lại b()
Frame c() không còn active. b() resume tại call site: c();
Vấn đề nằm ở đây: c() → 42 nhưng statement chỉ là: c();
Không có assignment. Không có expression khác dùng kết quả. Không có return.
Giá trị 42 vì vậy không được propagate lên caller tiếp theo.
b() đi đến cuối function body, nên: b() → undefined
Step 6 — Control quay lại a()
Call site trong a() là: b();
b() vừa tạo ra undefined, nhưng a() cũng không dùng và không return kết quả đó.
a() đi đến cuối body: a() → undefined
Step 7 — Global hoàn tất argument của console.log
Bây giờ expression ban đầu: console.log(a()); tương đương về mặt giá trị với: console.log(undefined);
Output: undefined
Quan sát quan trọng
42 không "biến mất vì frame c bị pop". c() đã tạo ra 42 thành công và trao kết quả về call site trong b(). Giá trị bị bỏ qua ở b() vì code chỉ viết c(); thay vì return c();.
Nếu muốn 42 đi xuyên qua chuỗi caller
function a() {
return b();
}
function b() {
return c();
}
function c() {
return 42;
}
console.log(a());Value flow lúc này là:
c() → 42
b() → 42
a() → 42
console.log(42)Output: 42
7. Prediction Exercise (Bài tập dự đoán)
Bài 1 — Return propagation
Đừng chạy code. Hãy dự đoán output và trace value flow.
function third() {
return 10;
}
function second() {
return third();
}
function first() {
second();
}
console.log(first());[Đáp án & Giải thích]
Output: undefined
- Giải thích:
third()trả10.second()dùngreturn third(), nênsecond()cũng trả10. Nhưngfirst()chỉ gọisecond();mà không return kết quả. Vìfirst()đi đến cuối body,first()trảundefined. Do đóconsole.log(first())inundefined.
Value flow:
third() → 10
second() → 10
first() → undefinedBài 2 — Nested syntax không đồng nghĩa simultaneous frames
Đừng chạy code. Hãy trả lời hai câu:
- Output là gì?
- Có thời điểm nào frame
addOnevàdoublecùng tồn tại trên Call Stack không?
function addOne(n) {
return n + 1;
}
function double(n) {
return n * 2;
}
function compute(n) {
return double(addOne(n));
}
console.log(compute(3));[Đáp án & Giải thích]
Output: 8
- Giải thích: Khi
compute(3)chạy, JavaScript cần giá trị argument chodouble(...). Vì vậyaddOne(3)chạy trước và trả4. Sau khiaddOnehoàn tất,double(4)mới được gọi và trả8.
Các trạng thái quan trọng:
Global → compute → addOne
Global → compute
Global → compute → double
Global → compute
GlobaladdOne và double không cần cùng active một lúc trong ví dụ này. Việc một call được viết bên trong argument của call khác không có nghĩa hai callee frame luôn chồng lên nhau.
Bài 3 — Transfer Check
Đừng chạy code. Tại dòng comment, function nào đang chờ và giá trị cuối cùng là gì?
function normalize(n) {
return n * 2;
}
function validate(n) {
const result = normalize(n);
return result > 10;
}
function submit(n) {
return validate(n); // ← trace tại đây
}
console.log(submit(6));[Đáp án & Giải thích]
- Giải thích: Khi
submit(6)gọivalidate(6), framesubmitvẫn đang chờ ở bên dưới. Bên trongvalidate,normalize(6)trả12, rồi framenormalizekết thúc.validateresume, evaluate12 > 10thànhtrue, rồi returntruechosubmit.submittiếp tục propagatetruelên Global.
Output: true
8. Implementation Lab (Bài lab thực hành)
Lab 1 — Trace Table (Guided)
Cho code:
function parse(value) {
return Number(value);
}
function clamp(value) {
const parsed = parse(value);
return parsed < 0 ? 0 : parsed;
}
function prepare(value) {
return clamp(value);
}
const result = prepare("-3");Hoàn thành trace bằng tay trước khi chạy code.
| Bước | Function đang chạy | Call Stack (đáy → đỉnh) | Value flow |
|---|---|---|---|
| 1 | Global | Global | Chưa có result |
| 2 | prepare("-3") | Global → prepare | Đang chờ clamp |
| 3 | clamp("-3") | Global → prepare → clamp | Đang chờ parse |
| 4 | parse("-3") | Global → prepare → clamp → parse | parse chuẩn bị trả -3 |
| 5 | clamp("-3") | Global → prepare → clamp | parsed = -3, rồi clamp trả 0 |
| 6 | prepare("-3") | Global → prepare | prepare nhận 0 và trả 0 |
| 7 | Global | Global | result = 0 |
[Đáp án Lab 1 — Trace Table]
Giải thích: Điểm quan trọng không phải nhớ bảng, mà là thấy caller luôn resume sau khi callee hoàn tất.
parsetrả-3choclamp;clampdùng giá trị đó để quyết định trả0;preparetiếp tục propagate0về Global.Tự kiểm tra: Nếu xóa
returntrongprepare, toàn bộ logic bên dưới vẫn chạy, nhưngresultsẽ trở thànhundefined.
Lab 2 — Repair the Return Chain (Partial Scaffold)
Đoạn code sau phải in:
HELLO, TUANNhưng hiện tại nó in undefined.
function normalizeName(name) {
return name.trim().toUpperCase();
}
function buildGreeting(name) {
const normalized = normalizeName(name);
`HELLO, ${normalized}`;
}
function getGreeting(name) {
buildGreeting(name);
}
console.log(getGreeting(" Tuan "));Nhiệm vụ:
- Không thay đổi tên function.
- Không gọi
normalizeNametrực tiếp từgetGreeting. - Chỉ thêm những
returncần thiết. - Trước khi sửa, trace value flow từ dưới lên.
Gợi ý
Đừng hỏi "function nào chạy đúng?". Hãy hỏi "mỗi call expression cuối cùng trả giá trị gì cho caller?".
[Đáp án Lab 2 — Repair the Return Chain]
Code tối thiểu:
function normalizeName(name) {
return name.trim().toUpperCase();
}
function buildGreeting(name) {
const normalized = normalizeName(name);
return `HELLO, ${normalized}`;
}
function getGreeting(name) {
return buildGreeting(name);
}
console.log(getGreeting(" Tuan "));- Giải thích:
normalizeNamevốn đã trả đúng"TUAN". Bug thứ nhất nằm ởbuildGreeting: template literal được evaluate nhưng không return. Bug thứ hai nằm ởgetGreeting: kết quả củabuildGreetingcũng không được propagate. Khi thêm haireturn, value flow trở thành"TUAN" → "HELLO, TUAN" → "HELLO, TUAN".
Lab 3 — Independent Trace
Không chạy code trong lượt đầu.
function base(n) {
return n + 2;
}
function left(n) {
return base(n) * 3;
}
function right(n) {
const value = left(n);
return value - 1;
}
const answer = right(4);Viết ra:
- thứ tự function được gọi;
- trạng thái stack sâu nhất;
- thứ tự function hoàn tất;
- giá trị được return tại từng tầng;
- giá trị cuối cùng của
answer.
[Đáp án Lab 3 — Independent Trace]
- Giải thích: Call order là
right(4) → left(4) → base(4). Stack sâu nhất làGlobal → right → left → base.base(4)trả6;left(4)resume và trả18;right(4)resume vớivalue = 18rồi trả17; cuối cùnganswer = 17.
Return order:
base → left → right → Global9. Edge Cases (Các trường hợp ngoại lệ)
Edge Case 1 — Lexical nesting không phải Call Stack nesting
function outer() {
function inner() {
return "done";
}
return inner;
}
const fn = outer();
console.log(fn());Sai lầm phổ biến
inner được khai báo bên trong outer, nhưng khi fn() được gọi ở dòng cuối, outer() đã hoàn tất từ trước. Vì vậy Call Stack lúc inner chạy không cần có frame outer.
Đừng đồng nhất:
function nằm bên trong function khác trong source codevới:
function đang được gọi trong khi caller khác còn activeEdge Case 2 — Expression tạo ra value nhưng value bị bỏ qua
function getPrice() {
return 99;
}
function logPrice() {
getPrice();
}
console.log(logPrice());Return value bị bỏ qua
getPrice() vẫn trả 99. Bug không nằm trong getPrice. logPrice() chỉ evaluate getPrice(); rồi đi đến cuối body, vì vậy logPrice() trả undefined.
Edge Case 3 — Nested call syntax có thể giảm stack trước khi call ngoài bắt đầu
function inner() {
return 5;
}
function outer(value) {
return value * 2;
}
console.log(outer(inner()));Trace quan trọng:
Global → inner
Global
Global → outer
GlobalEvaluation Boundary (Ranh giới đánh giá)
outer cần argument trước khi body của outer có thể chạy. inner() hoàn tất và tạo ra 5; sau đó outer(5) mới bắt đầu. Vì vậy nhìn code "lồng nhau" chưa đủ để suy ra các frame nào cùng tồn tại.
10. Debug Lab (Bài lab gỡ lỗi)
Symptom (Triệu chứng):
Một hàm checkout tính đúng subtotal ở tầng dưới, nhưng biến total ở tầng trên lại là undefined.
Reproduction (Tái hiện lỗi):
function calculateSubtotal(price, quantity) {
return price * quantity;
}
function buildOrder(price, quantity) {
calculateSubtotal(price, quantity);
}
function checkout(price, quantity) {
return buildOrder(price, quantity);
}
const total = checkout(7, 3);
console.log(total);Evidence (Bằng chứng):
calculateSubtotal(7, 3)cho kết quả21.checkout(7, 3)lại choundefined.- Không có exception; chương trình chạy hết.
Hypothesis (Giả thuyết):
Một caller ở giữa chuỗi đã nhận đúng value từ callee nhưng không propagate nó.
Verification (Xác minh):
Trace value flow:
calculateSubtotal(7, 3) → 21
buildOrder(7, 3) → ?
checkout(7, 3) → ?Đọc buildOrder:
function buildOrder(price, quantity) {
calculateSubtotal(price, quantity);
}21 được tạo ra nhưng không được return.
Vì vậy:
calculateSubtotal → 21
buildOrder → undefined
checkout → undefinedRoot Cause (Nguyên nhân gốc rễ):buildOrder thiếu return trước call expression calculateSubtotal(...).
Fix (Sửa lỗi):
function buildOrder(price, quantity) {
return calculateSubtotal(price, quantity);
}Prevention (Phòng ngừa):
- Khi viết wrapper function, xác định rõ contract: side effect only hay preserve return value.
- Khi debug
undefined, trace value từ callee sâu nhất đi ngược về caller. - Đừng dừng ở câu "hàm dưới trả đúng"; kiểm tra từng boundary giữa caller và callee.
11. Design / Decision Exercise (Bài tập lựa chọn cách triển khai)
Bạn cần viết wrapper để log một lần gọi function.
function withLog(fn, value) {
console.log("calling");
fn(value);
}function withLog(fn, value) {
console.log("calling");
return fn(value);
}Câu hỏi: Phiên bản nào đúng?
[Đáp án tham khảo]
Không có câu trả lời đúng tuyệt đối nếu chưa biết contract.
- Nếu
withLogchỉ có nhiệm vụ tạo side effect và caller không cần kết quả, phiên bản A có thể hợp lệ. - Nếu
withLogđược kỳ vọng là wrapper "trong suốt" — thêm logging nhưng vẫn giữ behavior về return value củafn— phiên bản B phù hợp hơn.
Ở depth L3–L4, điều cần chứng minh không phải kiến trúc wrapper nâng cao, mà là bạn biết return-value behavior là một phần của contract và có thể chọn implementation phù hợp.
Decision Lens (Góc nhìn quyết định)
Khi thêm một wrapper quanh function hiện có, hãy hỏi: "Caller cũ có đang phụ thuộc vào return value không?". Nếu có, bỏ return có thể làm thay đổi behavior dù callee vẫn chạy đúng.
12. Production Scenario (Tình huống thực tế)
Một utility được bọc thêm logging trước khi release:
function readFeatureFlag() {
return true;
}
function readFeatureFlagWithLog() {
console.log("feature flag read");
readFeatureFlag();
}
function canCheckout() {
return readFeatureFlagWithLog();
}
console.log(canCheckout());Trước khi thêm wrapper, code phía trên kỳ vọng nhận boolean. Sau thay đổi, canCheckout() trả undefined.
Câu hỏi:
- Function nào thực sự tính đúng giá trị?
- Boundary nào làm mất return value?
- Vì sao test chỉ kiểm tra "log có xuất hiện" có thể bỏ sót bug này?
- Bạn sẽ thêm test nào?
[Đáp án tham khảo]
readFeatureFlag()vẫn trảtrue.readFeatureFlagWithLog()gọi callee nhưng không return kết quả, nên wrapper trảundefined.- Test side effect chỉ chứng minh function được gọi và log xuất hiện; nó không chứng minh public behavior về return value còn nguyên.
- Thêm assertion kiểm tra
readFeatureFlagWithLog()và/hoặccanCheckout()trả đúng boolean mong đợi.
Bài học production: wrapper, instrumentation và adapter có thể làm hỏng value contract dù logic ở callee không đổi.
13. AI-Assisted Exercise (Bài tập với AI)
Level 2 — Challenge (Thách thức)
- Tự trả lời trước: Giải thích bằng 4–6 câu vì sao
c()trả42nhưnga()trong Worked Example lại trảundefined. - Hỏi AI: "In JavaScript nested calls, why can an inner function return 42 while the outermost function still returns undefined? Trace caller, callee, and return values."
- Challenge: Tìm xem AI có nói hoặc ngụ ý rằng
42"bị mất khi frame củacbị pop" hay không. - Verify: Đối chiếu lại với documentation chính thức về JavaScript
returnstatement và function return values (MDN hoặc ECMAScript specification).
Gợi ý
Điểm cần soi không phải chỉ là đáp án undefined. Hãy kiểm tra quan hệ nhân quả: c() đã trao 42 về call site trong b(); giá trị bị bỏ qua vì b() không dùng/return nó. Frame lifecycle và value propagation là hai chuyện liên quan nhưng không đồng nhất.
Đáp án tham khảo
Bạn nghĩ:
c()chạy và trả42cho expressionc()bên trongb(). Nhưngb()chỉ viếtc();, nên kết quả42không được dùng vàb()đi đến cuối body rồi trảundefined.a()lại chỉ gọib();, nên nó cũng trảundefined. Vì thế argument cuối cùng củaconsole.loglàundefined.AI trả lời: AI có thể nói: "
creturns 42, butbandadon't return that value, so it gets lost whenc's stack frame is popped and the outer functions default to undefined."So sánh: Phần "
bvàakhông return" là đúng. Phần gắn việc "mất42" với lúc framecbị pop là diễn đạt thiếu chính xác về causality.Điểm AI nói sai hoặc quá mơ hồ: Câu "
42gets lost whenc's stack frame is popped" dễ khiến bạn nghĩ pop frame làm mất return value. Thực tế,return 42đã làm call expressionc()tạo ra42cho caller. Chính statementc();trongb()không sử dụng giá trị đó.Kết luận: Nếu bạn phân biệt được frame lifecycle với return-value propagation, bạn đã nắm đúng mental model của nested calls. Phân biệt này sẽ quan trọng hơn nữa khi học recursion, exception unwinding và async execution.
14. Teach Back (Dạy lại)
Câu hỏi: Trong 2 phút, giải thích cho một đồng nghiệp junior vì sao "function sâu nhất return đúng" chưa đủ để kết luận "function ngoài cùng cũng return đúng". Dùng đoạn code sau và bắt buộc nói rõ caller, callee, call site, return value.
function c() {
return 42;
}
function b() {
c();
}
function a() {
return b();
}Mô phỏng
- Bạn nói:
blà caller củac, cònclà callee tại call sitec();.c()trả42, nên call expressionc()trongbcó giá trị42. Nhưngbkhôngreturnexpression đó; nó đi đến cuối function và trảundefined.agọib()bằngreturn b(), nênachỉ có thể propagate chính kết quả màb()trả — tứcundefined. Vì vậy muốn42đi tớia,bphải viếtreturn c();. Kết luận là mỗi caller boundary phải được trace riêng; return value không tự xuyên qua cả chuỗi.
💡 Hình dung mỗi function như một người chuyển hàng: người dưới đã giao đúng kiện
42chob, nhưng nếubkhông chuyển tiếp kiện hàng, người ở tầng trên vẫn nhận "không có hàng".
Gợi ý đánh giá bản thân
- Bạn có nói rõ giá trị bị bỏ qua ở call site nào, thay vì nói mơ hồ "return không đi lên" không?
- Người nghe có thể dùng lời giải thích của bạn để sửa
b()không? - Nếu đổi
a()thànhb();thay vìreturn b();, bạn có dự đoán ngay kết quả không?
15. Assessment (Đánh giá)
| Objective (Mục tiêu) | Hình thức đánh giá | Depth (Độ sâu) |
|---|---|---|
| Trace nested calls 3–4 tầng | Trace (Theo vết thực thi) | L3 |
| Dự đoán output và return-value flow | Prediction (Dự đoán) | L3 |
| Phân biệt control flow với value flow | Explain (Giải thích) | L2–L3 |
Debug missing return qua nhiều caller | Debug (Gỡ lỗi) | L4 |
| Phân biệt lexical nesting với invocation nesting | Classification (Phân loại) | L3 |
| Chọn wrapper có/không preserve return value theo contract | Implementation choice (Lựa chọn triển khai) | L3–L4 |
16. Exit Criteria (Tiêu chí qua bài)
- [ ] Có thể vẽ đúng Call Stack của nested calls 3–4 tầng tại một call site bất kỳ.
- [ ] Có thể ghi riêng call order và return order mà không nhầm hai thứ.
- [ ] Có thể trace return value qua từng caller boundary và xác định chính xác nơi value bị bỏ qua.
- [ ] Có thể dự đoán đúng ít nhất 4/5 scenario mới có nested calls và missing
return. - [ ] Có thể debug một wrapper trả
undefineddù callee sâu hơn trả đúng value. - [ ] Có thể giải thích vì sao lexical nesting trong source code không đồng nghĩa các function đó cùng tồn tại trên Call Stack.
- [ ] Có thể lựa chọn có/không
returntừ wrapper dựa trên contract thay vì áp dụng quy tắc máy móc.
17. Spiral Connection (Liên kết xoắn ốc)
Previous (Trước): Lesson 1.5.1 — Stack Frames. Bạn đã biết mỗi active invocation được biểu diễn trong trace model bằng một frame và có thể vẽ
push → execute → pop.
Current (Hiện tại): Nested Calls. Bạn thêm hai lớp reasoning lên Call Stack: control flow (caller/callee/resume) và value flow (call expression/return/propagation). Đây là bước chuyển từ "nhìn stack" sang "trace chương trình".
Next (Tiếp theo): Lesson 1.5.3 — Recursion sẽ tạo nhiều invocation nối tiếp của cùng một function và làm stack depth tăng. Lesson 1.5.4 — Exception & Stack Unwinding sẽ thay đổi cách control rời khỏi các frame. Lesson 1.5.5 sẽ hợp nhất Call Stack với scope, closure và value flow thành Full Execution Trace. Sau đó Call Stack quay lại ở Stage 3 khi học Async / Event Loop.