Generator function javascript là gì

Trong javascript một khi function được thực thi thì nó sẽ được đảm bảo

var x = 1;

function *foo() {
  x++;
  yield;
  console.log( "x:", x );
}

function bar() {
  x++;
}
3 tức là những phần code khác không thể can thiệp, làm gián đoạn quá trình chạy của function đó. Tuy nhiên ES6 đã cho ra mắt 1 loại function mới mà không hành xử theo lẽ thông thường như thế -
var x = 1;

function *foo() {
  x++;
  yield;
  console.log( "x:", x );
}

function bar() {
  x++;
}
4

Hãy xem xét ví dụ sau đây:

var x = 1;
function foo() {
  x++;
  bar();
  console.log( "x:", x );
}

function bar() {
  x++;
}

foo();

Chúng ta có thể thấy function

var x = 1;

function *foo() {
  x++;
  yield;
  console.log( "x:", x );
}

function bar() {
  x++;
}
5 được gọi bên trong function
var x = 1;

function *foo() {
  x++;
  yield;
  console.log( "x:", x );
}

function bar() {
  x++;
}
6 và giá trị của x sau khi gọi foo() là 3. Trong trường hợp không có lời gọi
var x = 1;

function *foo() {
  x++;
  yield;
  console.log( "x:", x );
}

function bar() {
  x++;
}
7 bên trong
var x = 1;

function *foo() {
  x++;
  yield;
  console.log( "x:", x );
}

function bar() {
  x++;
}
6 giá trị của
var x = 1;

function *foo() {
  x++;
  yield;
  console.log( "x:", x );
}

function bar() {
  x++;
}
9 sẽ là 2. Hãy tưởng tượng bằng 1 cách nào đó mặc dù chúng ta không gọi
var x = 1;

function *foo() {
  x++;
  yield;
  console.log( "x:", x );
}

function bar() {
  x++;
}
7 ở bên trong
var x = 1;

function *foo() {
  x++;
  yield;
  console.log( "x:", x );
}

function bar() {
  x++;
}
6 mà kết quả trả về vẫn là 3 ??? Đó chính là lúc mà chúng ta sử dụng
var it = foo();
it.next();
x;                      // 2
bar();
x;                      // 3
it.next();
2

Hãy thay đổi đoạn code 1 chút

var x = 1;

function *foo() {
  x++;
  yield;
  console.log( "x:", x );
}

function bar() {
  x++;
}

Bạn có để ý thấy sự khác biệt ?

Chúng ta đã có 1

var it = foo();
it.next();
x;                      // 2
bar();
x;                      // 3
it.next();
3 với khai báo
var it = foo();
it.next();
x;                      // 2
bar();
x;                      // 3
it.next();
4 - chú ý dấu
var it = foo();
it.next();
x;                      // 2
bar();
x;                      // 3
it.next();
5 trong phần khai báo.

Và sử dụng

var it = foo();
it.next();
x;                      // 2
bar();
x;                      // 3
it.next();
2 như sau

var it = foo();
it.next();
x;                      // 2
bar();
x;                      // 3
it.next();

Xem xét từng bước một của quá trình trên:

Như vậy chúng ta có thể gọi

var x = 1;

function *foo() {
  x++;
  yield;
  console.log( "x:", x );
}

function bar() {
  x++;
}
5 bên ngoài
var x = 1;

function *foo() {
  x++;
  yield;
  console.log( "x:", x );
}

function bar() {
  x++;
}
6 và kết quả cuối cùng của
var x = 1;

function *foo() {
  x++;
  yield;
  console.log( "x:", x );
}

function bar() {
  x++;
}
9 vẫn là 3. Chúng ta có thể coi điều này như là 1 sự phá vỡ rule
var x = 1;

function *foo() {
  x++;
  yield;
  console.log( "x:", x );
}

function bar() {
  x++;
}
3 của function

Input và output

var it = foo();
it.next();
x;                      // 2
bar();
x;                      // 3
it.next();
2 là 1 function đặc biệt nhưng nó vẫn là 1 function nên nó vẫn có thể có tham số truyền vào -
function *foo(x,y) {
  return x * y;
}

var it = foo( 6, 7 );

var res = it.next();

res.value;
2 và giá trị trả về -
function *foo(x,y) {
  return x * y;
}

var it = foo( 6, 7 );

var res = it.next();

res.value;
3 như 1 function thông thường.

function *foo(x,y) {
  return x * y;
}

var it = foo( 6, 7 );

var res = it.next();

res.value;

Chúng ta vẫn có thể truyền giá trị đầu vào 6 và 7 cho

var x = 1;

function *foo() {
  x++;
  yield;
  console.log( "x:", x );
}

function bar() {
  x++;
}
9 và
function *foo(x,y) {
  return x * y;
}

var it = foo( 6, 7 );

var res = it.next();

res.value;
5Cách gọi function cũng khá quen thuộc -
function *foo(x,y) {
  return x * y;
}

var it = foo( 6, 7 );

var res = it.next();

res.value;
6 nhưng cách mà chúng ta nhận về kết quả có đôi chút khác biệt.

Đầu tiên

function *foo(x,y) {
  return x * y;
}

var it = foo( 6, 7 );

var res = it.next();

res.value;
6 không thực thi function từ đầu đến cuối mà như đã đề cập lúc trước, nó tạo ra 1
function *foo(x,y) {
  return x * y;
}

var it = foo( 6, 7 );

var res = it.next();

res.value;
8. Khi
function *foo(x,y) {
  return x * y;
}

var it = foo( 6, 7 );

var res = it.next();

res.value;
9 được gọi, nó thực thi các lệnh trong function cho đến khi gặp
function *foo(x) {
  var y = x * (yield);
  return y;
}

var it = foo( 6 );

it.next();

var res = it.next( 7 );

res.value;      // 42
0 hoặc kết thúc function. Giá trị trả về của lênh
function *foo(x,y) {
  return x * y;
}

var it = foo( 6, 7 );

var res = it.next();

res.value;
9 này là 1 object có thuộc tính
function *foo(x) {
  var y = x * (yield);
  return y;
}

var it = foo( 6 );

it.next();

var res = it.next( 7 );

res.value;      // 42
2 và chúng ta sẽ lấy giá trị từ thuộc tính này.

Iteration Messaging

Với lệnh

function *foo(x) {
  var y = x * (yield);
  return y;
}

var it = foo( 6 );

it.next();

var res = it.next( 7 );

res.value;      // 42
0 đứng độc lập, ta có thể coi đó như 1 điểm dừng để thực hiện những xử lí chen vào giữa quá trình thực thi của function (có vẻ giống
function *foo(x) {
  var y = x * (yield);
  return y;
}

var it = foo( 6 );

it.next();

var res = it.next( 7 );

res.value;      // 42
4 mà mình vẫn hay dùng) nhưng thực thế là cách sử dụng của nó còn linh hoạt hơn thế.

function *foo(x) {
  var y = x * (yield);
  return y;
}

var it = foo( 6 );

it.next();

var res = it.next( 7 );

res.value;      // 42

Ở đây ta có thể sử dụng

function *foo(x) {
  var y = x * (yield);
  return y;
}

var it = foo( 6 );

it.next();

var res = it.next( 7 );

res.value;      // 42
0 như 1 nơi để truyền tham số vào Chuôĩ câu lệnh trên truyền 6 là giá trị đầu vào cho
var x = 1;

function *foo() {
  x++;
  yield;
  console.log( "x:", x );
}

function bar() {
  x++;
}
9 và truyền 7 như là gía trị cho
function *foo(x) {
  var y = x * (yield);
  return y;
}

var it = foo( 6 );

it.next();

var res = it.next( 7 );

res.value;      // 42
0 và gía trị sau khi tính toán là 6*7=42.

Câu chuyện ở đây là gì ? Câu lệnh

function *foo(x) {
  var y = x * (yield);
  return y;
}

var it = foo( 6 );

it.next();

var res = it.next( 7 );

res.value;      // 42
8 đầu tiên bắt đầu
var it = foo();
it.next();
x;                      // 2
bar();
x;                      // 3
it.next();
2 và chạy cho đến khi gặp
function *foo(x) {
  var y = x * (yield);
  return y;
}

var it = foo( 6 );

it.next();

var res = it.next( 7 );

res.value;      // 42
0. Khi gặp lệnh
function *foo(x) {
  var y = x * (yield);
  return y;
}

var it = foo( 6 );

it.next();

var res = it.next( 7 );

res.value;      // 42
0,
function *foo(x) {
  var y = x * (yield);
  return y;
}

var it = foo( 6 );

it.next();

var res = it.next( 7 );

res.value;      // 42
0 đặt ra câu hỏi giá trị truyền vào ở đây là gì. Câu lệnh
function *foo(x) {
  var y = x * (yield);
  return y;
}

var it = foo( 6 );

it.next();

var res = it.next( 7 );

res.value;      // 42
8 đầu tiên đã hoàn thành xong nhiệm vụ của mình vậy nên trách nhiệm trả lời này sẽ nằm trong câu lệnh
function *foo(x) {
  var y = x * (yield);
  return y;
}

var it = foo( 6 );

it.next();

var res = it.next( 7 );

res.value;      // 42
8 tiếp theo. Khi lệnh
function *foo(x) {
  var y = x * (yield);
  return y;
}

var it = foo( 6 );

it.next();

var res = it.next( 7 );

res.value;      // 42
8 tiếp theo được thực hiện, nó trả lời cho câu hỏi mà
function *foo(x) {
  var y = x * (yield);
  return y;
}

var it = foo( 6 );

it.next();

var res = it.next( 7 );

res.value;      // 42
0 đặt ra mà thực thi nốt phần còn lại.

Muliple iterators

Mỗi khi chúng ta tạo một iterator thì chúng ta cũng đã tạo 1 generator instance. Khi có nhiều instance của cùng 1 generator thì chúng có thể tương tác với nhau.

function *foo() {
  var x = yield 2;
  z++;
  var y = yield (x * z);
  console.log( x, y, z );
}

var z = 1;

var it1 = foo();
var it2 = foo();

var val1 = it1.next().value;            // 2 <-- yield 2
var val2 = it2.next().value;            // 2 <-- yield 2

val1 = it1.next( val2 * 10 ).value;     // 40  <-- x:20,  z:2
val2 = it2.next( val1 * 5 ).value;      // 600 <-- x:200, z:3

it1.next( val2 / 2 );                   // y:300
                                        // 20 300 3
it2.next( val1 / 4 );                   // y:10
                                        // 200 10 3

Trong đoạn code trên thì khi khởi tạo

function *foo() {
  var x = yield 2;
  z++;
  var y = yield (x * z);
  console.log( x, y, z );
}

var z = 1;

var it1 = foo();
var it2 = foo();

var val1 = it1.next().value;            // 2 <-- yield 2
var val2 = it2.next().value;            // 2 <-- yield 2

val1 = it1.next( val2 * 10 ).value;     // 40  <-- x:20,  z:2
val2 = it2.next( val1 * 5 ).value;      // 600 <-- x:200, z:3

it1.next( val2 / 2 );                   // y:300
                                        // 20 300 3
it2.next( val1 / 4 );                   // y:10
                                        // 200 10 3
7 và
function *foo() {
  var x = yield 2;
  z++;
  var y = yield (x * z);
  console.log( x, y, z );
}

var z = 1;

var it1 = foo();
var it2 = foo();

var val1 = it1.next().value;            // 2 <-- yield 2
var val2 = it2.next().value;            // 2 <-- yield 2

val1 = it1.next( val2 * 10 ).value;     // 40  <-- x:20,  z:2
val2 = it2.next( val1 * 5 ).value;      // 600 <-- x:200, z:3

it1.next( val2 / 2 );                   // y:300
                                        // 20 300 3
it2.next( val1 / 4 );                   // y:10
                                        // 200 10 3
8 chúng ta có 2 generator instance của generator
var x = 1;

function *foo() {
  x++;
  yield;
  console.log( "x:", x );
}

function bar() {
  x++;
}
6. Chú ý lệnh
var a = 1;
var b = 2;

function *foo() {
  a++;
  yield;
  b = b * a;
  a = (yield b) + 3;
}

function *bar() {
  b--;
  yield;
  a = (yield 8) + b;
  b = a * (yield 2);
}
0 có tác dụng như lệnh
function *foo(x) {
  var y = x * (yield);
  return y;
}

var it = foo( 6 );

it.next();

var res = it.next( 7 );

res.value;      // 42
0 không có tham số nhưng có thêm tác dụng gán
var a = 1;
var b = 2;

function *foo() {
  a++;
  yield;
  b = b * a;
  a = (yield b) + 3;
}

function *bar() {
  b--;
  yield;
  a = (yield 8) + b;
  b = a * (yield 2);
}
2vào thuộc tính
function *foo(x) {
  var y = x * (yield);
  return y;
}

var it = foo( 6 );

it.next();

var res = it.next( 7 );

res.value;      // 42
2 của iterator tại thời điểm câu lệnh
function *foo(x) {
  var y = x * (yield);
  return y;
}

var it = foo( 6 );

it.next();

var res = it.next( 7 );

res.value;      // 42
8 thực thi xong. Trong ví dụ trên giá trị của
function *foo() {
  var x = yield 2;
  z++;
  var y = yield (x * z);
  console.log( x, y, z );
}

var z = 1;

var it1 = foo();
var it2 = foo();

var val1 = it1.next().value;            // 2 <-- yield 2
var val2 = it2.next().value;            // 2 <-- yield 2

val1 = it1.next( val2 * 10 ).value;     // 40  <-- x:20,  z:2
val2 = it2.next( val1 * 5 ).value;      // 600 <-- x:200, z:3

it1.next( val2 / 2 );                   // y:300
                                        // 20 300 3
it2.next( val1 / 4 );                   // y:10
                                        // 200 10 3
7 và
function *foo() {
  var x = yield 2;
  z++;
  var y = yield (x * z);
  console.log( x, y, z );
}

var z = 1;

var it1 = foo();
var it2 = foo();

var val1 = it1.next().value;            // 2 <-- yield 2
var val2 = it2.next().value;            // 2 <-- yield 2

val1 = it1.next( val2 * 10 ).value;     // 40  <-- x:20,  z:2
val2 = it2.next( val1 * 5 ).value;      // 600 <-- x:200, z:3

it1.next( val2 / 2 );                   // y:300
                                        // 20 300 3
it2.next( val1 / 4 );                   // y:10
                                        // 200 10 3
8 có thể nhận iterator còn lại làm tham số trong quá trình tính toán của mình. Đối với trường hợp 2
function *foo(x,y) {
  return x * y;
}

var it = foo( 6, 7 );

var res = it.next();

res.value;
8 của 2
var it = foo();
it.next();
x;                      // 2
bar();
x;                      // 3
it.next();
2 khác nhau tùy theo cách thực hiện các step trong function của các generator mà kết quả trả về rất khác nhau. Hiện tượng
var a = 1;
var b = 2;

function *foo() {
  a++;
  yield;
  b = b * a;
  a = (yield b) + 3;
}

function *bar() {
  b--;
  yield;
  a = (yield 8) + b;
  b = a * (yield 2);
}
9 xảy ra và nó tương tự như việc xung đột tài nguyên giữa các thread trong các ngôn ngữ hỗ trợ multi-thread vậy.

Ví dụ với 2 generator sau:

var a = 1;
var b = 2;

function *foo() {
  a++;
  yield;
  b = b * a;
  a = (yield b) + 3;
}

function *bar() {
  b--;
  yield;
  a = (yield 8) + b;
  b = a * (yield 2);
}

Nếu

var x = 1;

function *foo() {
  x++;
  yield;
  console.log( "x:", x );
}

function bar() {
  x++;
}
6 và
var x = 1;

function *foo() {
  x++;
  yield;
  console.log( "x:", x );
}

function bar() {
  x++;
}
5 là 2 function thông thường thì kết quả khi thực thiện foo, bar sẽ chỉ có 2 case:

  • thực hiện
    var x = 1;
    
    function *foo() {
      x++;
      yield;
      console.log( "x:", x );
    }
    
    function bar() {
      x++;
    }
    
    6 trước,
    var x = 1;
    
    function *foo() {
      x++;
      yield;
      console.log( "x:", x );
    }
    
    function bar() {
      x++;
    }
    
    5 sau
  • thực hiện
    var x = 1;
    
    function *foo() {
      x++;
      yield;
      console.log( "x:", x );
    }
    
    function bar() {
      x++;
    }
    
    5 trước,
    var x = 1;
    
    function *foo() {
      x++;
      yield;
      console.log( "x:", x );
    }
    
    function bar() {
      x++;
    }
    
    6 sau

Nó chính là các case có thể xảy ra khi xử lí không đồng bộ

var x = 1;

function *foo() {
  x++;
  yield;
  console.log( "x:", x );
}

function bar() {
  x++;
}
6 và
var x = 1;

function *foo() {
  x++;
  yield;
  console.log( "x:", x );
}

function bar() {
  x++;
}
5. Nhưng với
var it = foo();
it.next();
x;                      // 2
bar();
x;                      // 3
it.next();
2 - thứ phá vỡ luật
var x = 1;

function *foo() {
  x++;
  yield;
  console.log( "x:", x );
}

function bar() {
  x++;
}
3, việc xen kẽ các step của
var x = 1;

function *foo() {
  x++;
  yield;
  console.log( "x:", x );
}

function bar() {
  x++;
}
6 và
var x = 1;

function *foo() {
  x++;
  yield;
  console.log( "x:", x );
}

function bar() {
  x++;
}
5 là điều có thể. Số lượng cách sắp xếp các step trộn lẫn với nhau cũng như số lượng kết quả trả về là khá nhiều và phức tạp.

Values

Generator Iterator

General iterator có thể sử dụng khi cần tính toán 1 chuỗi giá trị mà giá trị sau phụ thuộc vào giá trị trước. Mỗi lần gọi

function *foo(x) {
  var y = x * (yield);
  return y;
}

var it = foo( 6 );

it.next();

var res = it.next( 7 );

res.value;      // 42
8 chúng ta lại có thể nhận được 1 gía trị mới. Công việc này cũng có thể thực hiện bằng
 function *foo() {
   var nextVal;

   while (true) {
     if (nextVal === undefined) {
       nextVal = 1;
     }
     else {
       nextVal = (3 * nextVal) + 6;
     }

     yield nextVal;
   }cj
 }

 for (var v of foo()) {
   console.log( v );
   if (v > 500) {
     break;
   }
 }
 // 1 9 33 105 321 969
3.

 var foo = (function(){
   var nextVal;

   return function(){
     if (nextVal === undefined) {
       nextVal = 1;
     }
     else {
       nextVal = (3 * nextVal) + 6;
     }

     return nextVal;
   };
   })();

   foo();       // 1
   foo();       // 9
   foo();       // 33
   foo();       // 105

Mỗi lần gọi

var x = 1;

function *foo() {
  x++;
  yield;
  console.log( "x:", x );
}

function bar() {
  x++;
}
6 chúng ta nhận được 1 giá trị mới của chuỗi số.

Với phong cách của

var it = foo();
it.next();
x;                      // 2
bar();
x;                      // 3
it.next();
2 chúng ta có thể định nghĩa 1
var it = foo();
it.next();
x;                      // 2
bar();
x;                      // 3
it.next();
2 và cho chạy loop qua
var it = foo();
it.next();
x;                      // 2
bar();
x;                      // 3
it.next();
2 đó

 function *foo() {
   var nextVal;

   while (true) {
     if (nextVal === undefined) {
       nextVal = 1;
     }
     else {
       nextVal = (3 * nextVal) + 6;
     }

     yield nextVal;
   }cj
 }

 for (var v of foo()) {
   console.log( v );
   if (v > 500) {
     break;
   }
 }
 // 1 9 33 105 321 969

Cách viết này sử dụng vòng lặp

 function *foo() {
   var nextVal;

   while (true) {
     if (nextVal === undefined) {
       nextVal = 1;
     }
     else {
       nextVal = (3 * nextVal) + 6;
     }

     yield nextVal;
   }cj
 }

 for (var v of foo()) {
   console.log( v );
   if (v > 500) {
     break;
   }
 }
 // 1 9 33 105 321 969
8 - bình thường sẽ gây
 function *foo() {
   var nextVal;

   while (true) {
     if (nextVal === undefined) {
       nextVal = 1;
     }
     else {
       nextVal = (3 * nextVal) + 6;
     }

     yield nextVal;
   }cj
 }

 for (var v of foo()) {
   console.log( v );
   if (v > 500) {
     break;
   }
 }
 // 1 9 33 105 321 969
9 tuy nhiên với lệnh
function *foo(x) {
  var y = x * (yield);
  return y;
}

var it = foo( 6 );

it.next();

var res = it.next( 7 );

res.value;      // 42
0 đặt bên trong loop
var it = foo();
it.next();
x;                      // 2
bar();
x;                      // 3
it.next();
2 sẽ dừng lại tại mỗi lần lặp, lệnh
function *foo(x) {
  var y = x * (yield);
  return y;
}

var it = foo( 6 );

it.next();

var res = it.next( 7 );

res.value;      // 42
0 cũng giữ lại gía trị của function foo mà không cần đến closure để lưu lại biến trạng thái. Khi thực hiện lệnh
function foo(x,y,cb) {
  ajax("http://some.url.1/?x=" + x + "&y=" + y, cb);
}

foo( 11, 31, function(err,text) {
  if (err) {
    console.error( err );
  } else {
    console.log( text );
  }
});
3chúng ta vẫn có thể ngắt vòng lặp bằng cách check điều kiện và break.

Iterating Generators Asynchronously

Với các mẫu xử lí không đồng bộ,

var it = foo();
it.next();
x;                      // 2
bar();
x;                      // 3
it.next();
2 đem đến 1 cách tiếp cận mới. Dưới đây là 1 mẫu xử lí không đồng bộ kinh điển sử dụng câu lệnh ajax.

Javascript generator function là gì?

1) Generator function là gì Generator đóng vai trò cơ bản để xây dựng các phương thức xử lý bất đồng bộ khác (side effect), ví dụ: async/await, sagas. Generator function là một function, có khả năng tạm ngưng thực thi trước khi hàm kết thúc, và có thể tiếp tục chạy ở 1 thời điểm khác.

Generator object là gì?

Object generator này một iterator nên chúng ta có thể sử dụng nó trong vòng lặp for-of hoặc trong các fucntion khác chấp nhận đối số truyền vào iterable. Ở dòng 4 thì chúng ta gọi hàm next() trên generatorObject . Với lời gọi này thì generator bắt đầu được thực thi chức năng của nó.