Skip to content

Lesson 1.5.2 — Nested Calls ​

Bài 1.5.2 — Nested Calls
Nested Function Calls, Stack Growth & Return Flow • 31 phút
0:00 / 0:00

0. Metadata (Thông tin bài học) ​

Thuộc tínhGiá trị
Stage1 — JavaScript Execution Model
Module1.5 — Call Stack & Execution Tracing
Lesson1.5.2 — Nested Calls
CompetencyC02 — JavaScript Runtime (C02.6 Call Stack)
Depth TargetL3–L4 (Use / Debug)
PrerequisitesFunction call & return (Stage 0), Execution Context (Module 1.1), Stack Frames (Lesson 1.5.1)
Cognitive LoadMedium

1. Why This Exists (Vì sao cần học) ​

Bạn đọc đoạn code này:

js
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:

  1. 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.
  2. 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 expression kế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ể:

  1. Trace một chuỗi nested calls 3–4 tầng theo đúng thứ tự call, return và resume.
  2. Dự đoán output của code có nested calls mà không chạy chương trình.
  3. Phân biệt control flow với return-value flow.
  4. 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.
  5. Debug lỗi thiếu return trong một chuỗi wrapper/caller đơn giản.
  6. 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:

text
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 site

Với nhiều tầng:

text
CALL:
a()
 ↓
b()
 ↓
c()

RETURN:
c() → b() → a()

Nhưng có một chi tiết quan trọng:

text
return value
≠
tự động đi xuyên qua mọi caller

Mỗ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:

  1. Control: Function nào được gọi tiếp? Caller nào đang chờ?
  2. 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
CallerFunction đang thực hiện lời gọi đến function khác.
CalleeFunction được caller gọi.
Call siteVị trí expression nơi lời gọi function xảy ra.
Nested callMột function call xảy ra trong khi một function caller khác vẫn chưa hoàn tất.
ResumeCaller tiếp tục thực thi sau khi callee hoàn tất.
Return-value propagationCaller chủ động return kết quả của callee để chuyển giá trị lên tầng phía trên.
Implicit undefinedFunction đ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:

js
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:

text
Call Stack:
[ Global ]

Global bắt đầu evaluate a().

Step 2 — a() được gọi ​

text
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 ​

text
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 ​

text
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 ​

js
function a() {
  return b();
}

function b() {
  return c();
}

function c() {
  return 42;
}

console.log(a());

Value flow lúc này là:

text
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.

js
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ùng return third(), nên second() cũng trả 10. Nhưng first() chỉ gọi second(); mà không return kết quả. Vì first() đi đến cuối body, first() trả undefined. Do đó console.log(first()) in undefined.

Value flow:

text
third()  → 10
second() → 10
first()  → undefined

Bài 2 — Nested syntax không đồng nghĩa simultaneous frames ​

Đừng chạy code. Hãy trả lời hai câu:

  1. Output là gì?
  2. Có thời điểm nào frame addOne và double cùng tồn tại trên Call Stack không?
js
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 cho double(...). Vì vậy addOne(3) chạy trước và trả 4. Sau khi addOne hoàn tất, double(4) mới được gọi và trả 8.

Các trạng thái quan trọng:

text
Global → compute → addOne
Global → compute
Global → compute → double
Global → compute
Global

addOne 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ì?

js
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ọi validate(6), frame submit vẫn đang chờ ở bên dưới. Bên trong validate, normalize(6) trả 12, rồi frame normalize kết thúc. validate resume, evaluate 12 > 10 thành true, rồi return true cho submit. submit tiếp tục propagate true lên Global.

Output: true

8. Implementation Lab (Bài lab thực hành) ​

Lab 1 — Trace Table (Guided) ​

Cho code:

js
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ướcFunction đang chạyCall Stack (đáy → đỉnh)Value flow
1GlobalGlobalChưa có result
2prepare("-3")Global → prepareĐang chờ clamp
3clamp("-3")Global → prepare → clampĐang chờ parse
4parse("-3")Global → prepare → clamp → parseparse chuẩn bị trả -3
5clamp("-3")Global → prepare → clampparsed = -3, rồi clamp trả 0
6prepare("-3")Global → prepareprepare nhận 0 và trả 0
7GlobalGlobalresult = 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. parse trả -3 cho clamp; clamp dùng giá trị đó để quyết định trả 0; prepare tiếp tục propagate 0 về Global.

  • Tự kiểm tra: Nếu xóa return trong prepare, toàn bộ logic bên dưới vẫn chạy, nhưng result sẽ trở thành undefined.

Lab 2 — Repair the Return Chain (Partial Scaffold) ​

Đoạn code sau phải in:

text
HELLO, TUAN

Nhưng hiện tại nó in undefined.

js
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ụ:

  1. Không thay đổi tên function.
  2. Không gọi normalizeName trực tiếp từ getGreeting.
  3. Chỉ thêm những return cần thiết.
  4. 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:

js
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: normalizeName vố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ủa buildGreeting cũng không được propagate. Khi thêm hai return, value flow trở thành "TUAN" → "HELLO, TUAN" → "HELLO, TUAN".

Lab 3 — Independent Trace ​

Không chạy code trong lượt đầu.

js
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ới value = 18 rồi trả 17; cuối cùng answer = 17.

Return order:

text
base → left → right → Global

9. Edge Cases (Các trường hợp ngoại lệ) ​

Edge Case 1 — Lexical nesting không phải Call Stack nesting ​

js
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:

text
function nằm bên trong function khác trong source code

với:

text
function đang được gọi trong khi caller khác còn active

Edge Case 2 — Expression tạo ra value nhưng value bị bỏ qua ​

js
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 ​

js
function inner() {
  return 5;
}

function outer(value) {
  return value * 2;
}

console.log(outer(inner()));

Trace quan trọng:

text
Global → inner
Global
Global → outer
Global

Evaluation 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):

js
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 cho undefined.
  • 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:

text
calculateSubtotal(7, 3) → 21
buildOrder(7, 3)        → ?
checkout(7, 3)          → ?

Đọc buildOrder:

js
function buildOrder(price, quantity) {
  calculateSubtotal(price, quantity);
}

21 được tạo ra nhưng không được return.

Vì vậy:

text
calculateSubtotal → 21
buildOrder        → undefined
checkout          → undefined

Root Cause (Nguyên nhân gốc rễ):
buildOrder thiếu return trước call expression calculateSubtotal(...).

Fix (Sửa lỗi):

js
function buildOrder(price, quantity) {
  return calculateSubtotal(price, quantity);
}

Prevention (Phòng ngừa):

  1. Khi viết wrapper function, xác định rõ contract: side effect only hay preserve return value.
  2. Khi debug undefined, trace value từ callee sâu nhất đi ngược về caller.
  3. Đừ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.

js
function withLog(fn, value) {
  console.log("calling");
  fn(value);
}
js
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 withLog chỉ 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ủa fn — 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:

js
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:

  1. Function nào thực sự tính đúng giá trị?
  2. Boundary nào làm mất return value?
  3. Vì sao test chỉ kiểm tra "log có xuất hiện" có thể bỏ sót bug này?
  4. Bạn sẽ thêm test nào?
[Đáp án tham khảo]
  1. readFeatureFlag() vẫn trả true.
  2. readFeatureFlagWithLog() gọi callee nhưng không return kết quả, nên wrapper trả undefined.
  3. 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.
  4. Thêm assertion kiểm tra readFeatureFlagWithLog() và/hoặc canCheckout() 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)

  1. Tự trả lời trước: Giải thích bằng 4–6 câu vì sao c() trả 42 nhưng a() trong Worked Example lại trả undefined.
  2. 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."
  3. Challenge: Tìm xem AI có nói hoặc ngụ ý rằng 42 "bị mất khi frame của c bị pop" hay không.
  4. Verify: Đối chiếu lại với documentation chính thức về JavaScript return statement 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ả 42 cho expression c() bên trong b(). Nhưng b() chỉ viết c();, nên kết quả 42 không được dùng và b() đi đến cuối body rồi trả undefined. a() lại chỉ gọi b();, nên nó cũng trả undefined. Vì thế argument cuối cùng của console.log là undefined.

  • AI trả lời: AI có thể nói: "c returns 42, but b and a don't return that value, so it gets lost when c's stack frame is popped and the outer functions default to undefined."

  • So sánh: Phần "b và a không return" là đúng. Phần gắn việc "mất 42" với lúc frame c bị 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 "42 gets lost when c'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 expression c() tạo ra 42 cho caller. Chính statement c(); trong b() 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.

js
function c() {
  return 42;
}

function b() {
  c();
}

function a() {
  return b();
}
Mô phỏng
  • Bạn nói: b là caller của c, còn c là callee tại call site c();. c() trả 42, nên call expression c() trong b có giá trị 42. Nhưng b không return expression đó; nó đi đến cuối function và trả undefined. a gọi b() bằng return b(), nên a chỉ có thể propagate chính kết quả mà b() trả — tức undefined. Vì vậy muốn 42 đi tới a, b phải viết return 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 42 cho b, nhưng nếu b khô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ành b(); 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ầngTrace (Theo vết thực thi)L3
Dự đoán output và return-value flowPrediction (Dự đoán)L3
Phân biệt control flow với value flowExplain (Giải thích)L2–L3
Debug missing return qua nhiều callerDebug (Gỡ lỗi)L4
Phân biệt lexical nesting với invocation nestingClassification (Phân loại)L3
Chọn wrapper có/không preserve return value theo contractImplementation 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ả undefined dù 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 return từ 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.

📴 Offline Mode — Content served from cache