promise의 경우, promise 의 콜백함수 안에 동기처리 하고싶은 비동기 처리를 담는 것이라는 점입니다.
콜백의 경우, 비동기가 예상되는 함수A의 인수로써 그 이후에 처리되어야 하는 함수B를 넣으므로, A->B처리되게 하는 것
//선언부
constfunc1 = function() {
returnnewPromise(function(resolve, reject){
...(동기처리 하고 싶은 비동기 처리)...
});
};
function A(callback){
...
callback(param1,param2);
}
1.then() 메서드는 Promise를 리턴하고 두개의 콜백함수를 인수로 받습니다. 이때, 인수로 들어온 콜백함수의 리턴값이 promise객체일 때, then()이 콜백함수의 리턴값인 promise객체를 받습니다.
[이상경우]
//선언부
constfunc1 = function() {
returnnewPromise(function(resolve, reject){
setTimeout(function(){
console.log('a');
resolve();
//reject('func1 fail');
}, 2000);
});
};
constfunc2 = function() {
returnnewPromise(function(resolve, reject){
setTimeout(function(){
console.log('b');
resolve();
//reject('func2 fail');
}, 3000);
});
};
constfunc3 = function() {
console.log('c');
};
//실행부
func1()
.then(func2())
.then(func3)
//기대치는 a b c인데 a c b 반환
//then의 파라미터는 함수가 들어가야 하는데, func2()는 promise객체이기 때문에 .then(func2())이 작동하지 않음
[정상경우]
//선언부
constfunc1 = function() {
returnnewPromise(function(resolve, reject){
setTimeout(function(){
console.log('a');
resolve();
//reject('func1 fail');
}, 2000);
});
};
constfunc2 = function() {
returnnewPromise(function(resolve, reject){
setTimeout(function(){
console.log('b');
resolve();
//reject('func2 fail');
}, 3000);
});
};
constfunc3 = function() {
console.log('c');
};
//실행부
func1()
.then(func2)
.then(func3)
// a b c 반환
func1()
.then(function(){returnfunc2()})
.then(func3)
// a b c 반환
func1()
.then(function(){func2()})
.then(func3)
// a c b반환
// function(){func2()}는 return값이 없기때문에 제대로 작동하지 않는다.
2. .then() 메서드의 파라미터 두개중, 하나는 Promise가 성공(success)했을 때를 위한 콜백 함수이고, 다른 하나는 실패(failure)했을 때를 위한 콜백 함수입니다.
promise 선언부에서 resolve 콜백함수를 두고, 이 콜백함수를 정의하는 부분이 then(resolve,reject)에서 resolve부분입니다. 따라서, then(resolve,reject)에서 resolve를 정의하기 위해선 선언부의 resolve콜백함수는 필수적으로 존재해야합니다.
//선언부
constfunc1 = function() {
returnnewPromise(function(resolve, reject){
setTimeout(function(){
console.log('a');
//resolve();
//reject('func1 fail');
}, 2000);
});
};
constfunc2 = function() {
returnnewPromise(function(resolve, reject){
setTimeout(function(){
console.log('b');
//resolve();
//reject('func2 fail');
}, 3000);
});
};
constfunc3 = function() {
console.log('c');
};
//실행부
func1()
.then(func2)
.then(func3)
//기대값은 a b c 인데 a만 반환된다
//resolve()함수가 선언되지 않았기 때문에, 시행되지 않기 때문이다.
3.
constfunc1 = (num) => {
returnnewPromise((resolve,reject) => {
setTimeout(() => {
console.log('a');
resolve(num);
}, 3000);
});
};
constfunc2 = (num) => {
returnnewPromise((resolve,reject) => {
setTimeout(() => {
console.log('b');
resolve(num * num);
}, 2000);
});
};
constfunc3 = (num) => {
returnnewPromise((resolve,reject) => {
setTimeout(() => {
console.log('c');
resolve(num * num * num);
}, 2000);
});
};
//실행부1
func1()
.then(func2)
.then(func3)
.then((result)=>{console.log(result)})
//실행부2
func1(2)
.then((result)=>{
func2(result).then((result) => {
func3(result).then((result)=>{
console.log(result);
});
})
})
4. then자체는 비동기 처리가 되는 함수이다.
func1, func2, func3를 순서대로 동기처리 하기 위해서는 promise 체인 안에 세 함수를 위치시켜야 한다.
동일한 promise 체인안에 위치하는 func1과 func2는 동기 처리 되지만, func3는 동일한 promise체인 안에 존재하지 않으므로
1. 비동기적으로 func3의 결과를 반환 후
2. func1 -> func2의 결과를 반환한다.
//선언부
constfunc1 = function() {
returnnewPromise(function(resolve, reject){
setTimeout(function(){
console.log('a');
resolve();
//reject('func1 fail');
}, 2000);
});
};
constfunc2 = function() {
returnnewPromise(function(resolve, reject){
setTimeout(function(){
console.log('b');
resolve();
//reject('func2 fail');
}, 3000);
});
};
constfunc3 = function() {
console.log('c');
};
//실행부
func1()
.then(func2)
func3()
// c a b 반환
//then의 파라미터는 함수가 들어가야 하는데, func2()는 promise객체이기 때문에 .then(func2())이 작동하지 않음
Class inheritance, super https://javascript.info/class-inheritance
Classes can extend one another. There’s a nice syntax, technically based on the prototypal inheritance.
To inherit from another class, we should specify "extends" and the parent class before the brackets {..}.
Here Rabbit inherits from Animal:
classAnimal{constructor(name){this.speed =0;this.name = name;}run(speed){this.speed += speed;alert(`${this.name} runs with speed ${this.speed}.`);}stop(){this.speed =0;alert(`${this.name} stopped.`);}}// Inherit from AnimalclassRabbitextendsAnimal{hide(){alert(`${this.name} hides!`);}}let rabbit =newRabbit("White Rabbit");
rabbit.run(5);// White Rabbit runs with speed 5.
rabbit.hide();// White Rabbit hides!
The extends keyword actually adds a [[Prototype]] reference from Rabbit.prototype to Animal.prototype, just as you expect it to be, and as we’ve seen before.
So now rabbit has access both to its own methods and to methods of Animal.
Any expression is allowed after extends
Class syntax allows to specify not just a class, but any expression after extends.
For instance, a function call that generates the parent class:
Now let’s move forward and override a method. As of now, Rabbit inherits the stop method that sets this.speed = 0 from Animal.
If we specify our own stop in Rabbit, then it will be used instead:
classRabbitextendsAnimal{stop(){// ...this will be used for rabbit.stop()}}
…But usually we don’t want to totally replace a parent method, but rather to build on top of it, tweak or extend its functionality. We do something in our method, but call the parent method before/after it or in the process.
Classes provide "super" keyword for that.
super.method(...) to call a parent method.
super(...) to call a parent constructor (inside our constructor only).
For instance, let our rabbit autohide when stopped:
classAnimal{constructor(name){this.speed =0;this.name = name;}run(speed){this.speed += speed;alert(`${this.name} runs with speed ${this.speed}.`);}stop(){this.speed =0;alert(`${this.name} stopped.`);}}classRabbitextendsAnimal{hide(){alert(`${this.name} hides!`);}stop(){super.stop();// call parent stopthis.hide();// and then hide}}let rabbit =newRabbit("White Rabbit");
rabbit.run(5);// White Rabbit runs with speed 5.
rabbit.stop();// White Rabbit stopped. White rabbit hides!
Now Rabbit has the stop method that calls the parent super.stop() in the process.
Till now, Rabbit did not have its own constructor.
According to the specification, if a class extends another class and has no constructor, then the following constructor is generated:
classRabbitextendsAnimal{// generated for extending classes without own constructorsconstructor(...args){super(...args);}}
As we can see, it basically calls the parent constructor passing it all the arguments. That happens if we don’t write a constructor of our own.
Now let’s add a custom constructor to Rabbit. It will specify the earLength in addition to name:
classAnimal{constructor(name){this.speed =0;this.name = name;}// ...}classRabbitextendsAnimal{constructor(name, earLength){this.speed =0;this.name = name;this.earLength = earLength;}// ...}// Doesn't work!let rabbit =newRabbit("White Rabbit",10);// Error: this is not defined.
Whoops! We’ve got an error. Now we can’t create rabbits. What went wrong?
The short answer is: constructors in inheriting classes must call super(...), and (!) do it before using this.
…But why? What’s going on here? Indeed, the requirement seems strange.
Of course, there’s an explanation. Let’s get into details, so you’d really understand what’s going on.
In JavaScript, there’s a distinction between a “constructor function of an inheriting class” and all others. In an inheriting class, the corresponding constructor function is labelled with a special internal property [[ConstructorKind]]:"derived".
The difference is:
When a normal constructor runs, it creates an empty object as this and continues with it.
But when a derived constructor runs, it doesn’t do it. It expects the parent constructor to do this job.
So if we’re making a constructor of our own, then we must call super, because otherwise the object with this reference to it won’t be created. And we’ll get an error.
For Rabbit to work, we need to call super() before using this, like here:
classAnimal{constructor(name){this.speed =0;this.name = name;}// ...}classRabbitextendsAnimal{constructor(name, earLength){super(name);this.earLength = earLength;}// ...}// now finelet rabbit =newRabbit("White Rabbit",10);alert(rabbit.name);// White Rabbitalert(rabbit.earLength);// 10
Let’s get a little deeper under the hood of super. We’ll see some interesting things by the way.
First to say, from all that we’ve learned till now, it’s impossible for super to work.
Yeah, indeed, let’s ask ourselves, how it could technically work? When an object method runs, it gets the current object as this. If we call super.method() then, how to retrieve the method? Naturally, we need to take the method from the prototype of the current object. How, technically, we (or a JavaScript engine) can do it?
Maybe we can get the method from [[Prototype]] of this, as this.__proto__.method? Unfortunately, that doesn’t work.
Let’s try to do it. Without classes, using plain objects for the sake of simplicity.
Here, rabbit.eat() should call animal.eat() method of the parent object:
let animal ={
name:"Animal",eat(){alert(`${this.name} eats.`);}};let rabbit ={
__proto__: animal,
name:"Rabbit",eat(){// that's how super.eat() could presumably workthis.__proto__.eat.call(this);// (*)}};
rabbit.eat();// Rabbit eats.
At the line (*) we take eat from the prototype (animal) and call it in the context of the current object. Please note that .call(this) is important here, because a simple this.__proto__.eat() would execute parent eat in the context of the prototype, not the current object.
And in the code above it actually works as intended: we have the correct alert.
Now let’s add one more object to the chain. We’ll see how things break:
let animal ={
name:"Animal",eat(){alert(`${this.name} eats.`);}};let rabbit ={
__proto__: animal,eat(){// ...bounce around rabbit-style and call parent (animal) methodthis.__proto__.eat.call(this);// (*)}};let longEar ={
__proto__: rabbit,eat(){// ...do something with long ears and call parent (rabbit) methodthis.__proto__.eat.call(this);// (**)}};
longEar.eat();// Error: Maximum call stack size exceeded
The code doesn’t work anymore! We can see the error trying to call longEar.eat().
It may be not that obvious, but if we trace longEar.eat() call, then we can see why. In both lines (*) and (**) the value of this is the current object (longEar). That’s essential: all object methods get the current object as this, not a prototype or something.
So, in both lines (*) and (**) the value of this.__proto__ is exactly the same: rabbit. They both call rabbit.eatwithout going up the chain in the endless loop.
Here’s the picture of what happens:
Inside longEar.eat(), the line (**) calls rabbit.eat providing it with this=longEar.
// inside longEar.eat() we have this = longEarthis.__proto__.eat.call(this)// (**)// becomes
longEar.__proto__.eat.call(this)// that is
rabbit.eat.call(this);
Then in the line (*) of rabbit.eat, we’d like to pass the call even higher in the chain, but this=longEar, so this.__proto__.eat is again rabbit.eat!
// inside rabbit.eat() we also have this = longEarthis.__proto__.eat.call(this)// (*)// becomes
longEar.__proto__.eat.call(this)// or (again)
rabbit.eat.call(this);
…So rabbit.eat calls itself in the endless loop, because it can’t ascend any further.
To provide the solution, JavaScript adds one more special internal property for functions: [[HomeObject]].
When a function is specified as a class or object method, its [[HomeObject]] property becomes that object.
This actually violates the idea of “unbound” functions, because methods remember their objects. And [[HomeObject]] can’t be changed, so this bound is forever. So that’s a very important change in the language.
But this change is safe. [[HomeObject]] is used only for calling parent methods in super, to resolve the prototype. So it doesn’t break compatibility.
Let’s see how it works for super – again, using plain objects:
Every method remembers its object in the internal [[HomeObject]] property. Then super uses it to resolve the parent prototype.
[[HomeObject]] is defined for methods defined both in classes and in plain objects. But for objects, methods must be specified exactly the given way: as method(), not as "method: function()".
In the example below a non-method syntax is used for comparison. [[HomeObject]] property is not set and the inheritance doesn’t work:
let animal ={
eat:function(){// should be the short syntax: eat() {...}// ...}};let rabbit ={
__proto__: animal,
eat:function(){super.eat();}};
rabbit.eat();// Error calling super (because there's no [[HomeObject]])
The class syntax supports inheritance for static properties too.
For instance:
classAnimal{constructor(name, speed){this.speed = speed;this.name = name;}run(speed =0){this.speed += speed;alert(`${this.name} runs with speed ${this.speed}.`);}staticcompare(animalA, animalB){return animalA.speed - animalB.speed;}}// Inherit from AnimalclassRabbitextendsAnimal{hide(){alert(`${this.name} hides!`);}}let rabbits =[newRabbit("White Rabbit",10),newRabbit("Black Rabbit",5)];
rabbits.sort(Rabbit.compare);
rabbits[0].run();// Black Rabbit runs with speed 5.
Now we can call Rabbit.compare assuming that the inherited Animal.compare will be called.
How does it work? Again, using prototypes. As you might have already guessed, extends also gives Rabbit the [[Prototype]] reference to Animal.
So, Rabbit function now inherits from Animal function. And Animal function normally has [[Prototype]] referencing Function.prototype, because it doesn’t extend anything.
Here, let’s check that:
classAnimal{}classRabbitextendsAnimal{}// for static propertites and methodsalert(Rabbit.__proto__ === Animal);// true// and the next step is Function.prototypealert(Animal.__proto__ === Function.prototype);// true// that's in addition to the "normal" prototype chain for object methodsalert(Rabbit.prototype.__proto__ === Animal.prototype);
This way Rabbit has access to all static methods of Animal.
Please note that built-in classes don’t have such static [[Prototype]] reference. For instance, Object has Object.defineProperty, Object.keys and so on, but Array, Date etc do not inherit them.
Here’s the picture structure for Date and Object:
Note, there’s no link between Date and Object. Both Object and Date exist independently. Date.prototype inherits from Object.prototype, but that’s all.
Such difference exists for historical reasons: there was no thought about class syntax and inheriting static methods at the dawn of JavaScript language.
Built-in classes like Array, Map and others are extendable also.
For instance, here PowerArray inherits from the native Array:
// add one more method to it (can do more)classPowerArrayextendsArray{isEmpty(){returnthis.length ===0;}}let arr =newPowerArray(1,2,5,10,50);alert(arr.isEmpty());// falselet filteredArr = arr.filter(item => item >=10);alert(filteredArr);// 10, 50alert(filteredArr.isEmpty());// false
Please note one very interesting thing. Built-in methods like filter, map and others – return new objects of exactly the inherited type. They rely on the constructor property to do so.
In the example above,
arr.constructor === PowerArray
So when arr.filter() is called, it internally creates the new array of results exactly as new PowerArray. And we can keep using its methods further down the chain.
Even more, we can customize that behavior. The static getter Symbol.species, if exists, returns the constructor to use in such cases.
For example, here due to Symbol.species built-in methods like map, filter will return “normal” arrays:
classPowerArrayextendsArray{isEmpty(){returnthis.length ===0;}// built-in methods will use this as the constructorstaticget[Symbol.species](){return Array;}}let arr =newPowerArray(1,2,5,10,50);alert(arr.isEmpty());// false// filter creates new array using arr.constructor[Symbol.species] as constructorlet filteredArr = arr.filter(item => item >=10);// filteredArr is not PowerArray, but Arrayalert(filteredArr.isEmpty());// Error: filteredArr.isEmpty is not a function
We can use it in more advanced keys to strip extended functionality from resulting values if not needed. Or, maybe, to extend it even further.
Unfortunately, Rabbit objects can’t be created. What’s wrong? Fix it.
classAnimal{constructor(name){this.name = name;}}classRabbitextendsAnimal{constructor(name){this.name = name;this.created = Date.now();}}let rabbit =newRabbit("White Rabbit");// Error: this is not definedalert(rabbit.name);
We’ve got a Clock class. As of now, it prints the time every second.
Create a new class ExtendedClock that inherits from Clock and adds the parameter precision – the number of msbetween “ticks”. Should be 1000 (1 second) by default.
Generator는 빠져나갔다가 중간에 처리를 멈추어 놓고 나중에 다시 돌아올 수 있는 함수입니다. 이때 컨텍스트(변수 값)는 출입 과정에서 저장된 상태로 남아 있습니다.
Generator 함수는 호출되어도 즉시 실행되지 않고, 대신 함수를 위한 Iterator 객체가 반환됩니다. Iterator의 next() 메서드를 호출하면 Generator 함수가 실행되어 yield 문을 만날 때까지 진행하고, 해당 표현식이 명시하는 Iterator로부터의 반환값을 반환합니다. yield* 표현식을 마주칠 경우, 다른 Generator 함수가 위임(delegate)되어 진행됩니다.
이후 next() 메서드가 호출되면 진행이 멈췄던 위치에서부터 재실행합니다. next() 가 반환하는 객체는 yield문이 반환할 값(yielded value)을 나타내는 value 속성과, Generator 함수 안의 모든 yield 문의 실행 여부를 표시하는 boolean 타입의 done 속성을 갖습니다. next() 를 인자값과 함께 호출할 경우, 진행을 멈췄던 위치의 yield 문을 next() 메서드에서 받은 인자값으로 치환하고 그 위치에서 다시 실행하게 됩니다.
generator와 yield는 promise와는 아무런 관련이 없다. yield는 그 자체만으로는 동기처리를 해주는 마법의 도구가 아니다. 결국 동기처리를 위해서는 promise가 필요하며, 중지와 재개라는 성격을 가진 generator-yield는 promise처리를 보기 쉽게 도와주는 기능만 할 뿐이다.
예시
간단한 예제
function*idMaker(){var index =0;while(index <3)yield index++;}var gen =idMaker();
console.log(gen.next().value);// 0
console.log(gen.next().value);// 1
console.log(gen.next().value);// 2
console.log(gen.next().value);// undefined// ...
yield* 를 사용한 예제
function*anotherGenerator(i){yield i +1;yield i +2;yield i +3;}function*generator(i){yield i;yield*anotherGenerator(i);yield i +10;}var gen =generator(10);
console.log(gen.next().value);// 10
console.log(gen.next().value);// 11
console.log(gen.next().value);// 12
console.log(gen.next().value);// 13
console.log(gen.next().value);// 20
Generator 에 인자값을 넘기는 예제
function*logGenerator(){
console.log(yield);
console.log(yield);
console.log(yield);}var gen =logGenerator();// the first call of next executes from the start of the function// until the first yield statement
gen.next();
gen.next('pretzel');// pretzel
gen.next('california');// california
gen.next('mayonnaise');// mayonnaise
Generator 는 생성자로서 사용될 수 없다
function* f() {}
var obj = new f; // throws "TypeError: f is not a constructor"
・yieldp(1)의 결과값은 promise의 resolve()에서 ()괄호 안의 값이 된다. ・왜 위의 소스 코드에서는 generator를 .next 하지 않았는데, yield값이 반환되는지 의아할 것이다. 사실 .next로 제너레이터를 일일이 핸들링 하는 것은 매우 귀찮고 까다로운 일이다. 이를 자동으로 해주는 모듈이 co 모듈이다. co모듈 안에 제너레이터를 담으면, 제너레이터의 yield 이후의 구문이 끝날때 까지 기다렸다가 자동으로 다음 yield로 넘겨준다.
・co 모듈의 가장 좆되는 점은, 가장 상위의 모듈에서만 co를 사용하면, 상위의 모듈에서 import하는 하위의 모듈에서 까지 yield를 자동 next처리 해준다는 점이다!! 이때 알아야 하는 것은 언제 co가 next를 호출해 주느냐 하는 것인데, co는 yield 이후 함수와 그 함수와 연결된 함수들을 체인처럼 쭉 보고 있다가 promise함수가 끝나면 next()를 자동으로 처리하게 되어 다음 yield로 순번을 넘겨준다. 위의 예에서보면, generatorTest2.js는 co모듈을 따로 사용하고 있지 않지만, co모듈을 사용하는 generatorTest1.js에서 호출 되고 있기 때문에 next()처리를 자동으로 하게된다. ・yieldp(1)의 결과값은 promise의 resolve()에서 ()괄호 안의 값이 된다. ・왜 위의 소스 코드에서는 generator를 .next 하지 않았는데, yield값이 반환되는지 의아할 것이다. 사실 .next로 제너레이터를 일일이 핸들링 하는 것은 매우 귀찮고 까다로운 일이다. 이를 자동으로 해주는 모듈이 co 모듈이다. co모듈 안에 제너레이터를 담으면, 제너레이터의 yield 이후의 구문이 끝날때 까지 기다렸다가 자동으로 다음 yield로 넘겨준다.
ECMAScript 2015(a.k.a. ES6)에서 새롭고 많은 기능들이 추가되었다. 대부분은 이해하기 쉽지만, Iterator와 Generator를 이해하기 위해선 조금 더 노력이 필요하다. 이 글은 여러분이 Iterator와 Generator를 활용할 수 있도록 가이드(base line)를 제공한다.
(역: 예제코드는 NodeJS-v4.2.1(V8) 지원 문법을 기준으로 변경하였다. 원문은 ES6, Firefox 지원 문법 기준)
이 글은 ES6의 문법인 arrow, class, destructing, for-of, let/const, spread operator등을 사용하였는데, 문법들에 대한 이해가 필요하다면 아래 사이트들을 참고하도록 한다.
Iterator는 next메서드를 가지고있는 객체이다. 이 메서드는 순차적으로 원소들을 탐색하며, next메서드의 호출시마다 새로운 객체를 반환한다. 반환되는 객체는 value 와(또는 = and/or) done 프로퍼티를 가지고 있으며, 탐색이 완료될 때 done프로퍼티의 값이 true가 된다. 대부분의 Iterator들은 탐색이 완료될때, value를 생략한다. —> return {done: true};
Iterable은 Symbol.iterator라는 메서드를 가지고있는 객체이다. 이 메서드가 Iterator를 반환한다.
Iterable/Iterator Example
피보나치 수열을 생성하는 코드를 작성하였다. fibonacci변수가 참조하고 있는 객체는 Iterable이다.
Array(typed array 포함), Set, Map의 아래 메서드들은 Iterator를 반환한다.
entries - key,value쌍 [key, value]
keys
values
이 메서드들에서 리턴되는 객체는 Iterable이면서 Iterator이다. Array에서 key는 인덱스들을 나타내며, Set에서는 key,value가 둘 다 value로 같다.
커스텀 객체는 Symbol.iterator 메서드를 추가함으로써 iterable이 될 수 있다.
functionobjectEntries(obj) {
let index =0,
keys =Object.getOwnPropertyNames(obj)
.concat(Object.getOwnPropertySymbols(obj)); // Reflect.ownKeys(obj);return {
[Symbol.iterator]() {
returnthis;
},
next() {
if (index ===keys.length) {
return {done:true};
} else {
let k = keys[index],
v = obj[k];
index +=1;
return {value: [k, v]};
}
}
};
}
let obj = {foo:1, bar:2, baz:3};
for (constpairofobjectEntries(obj)) { // for (const [k, v] of objectEntries(obj))console.log(pair[0], 'is', pair[1]); // console.log(k, 'is', v);
}
Iterable Consumers
다음 문법들은 Iterable을 사용한다.
for-of loop
for (constvalueof someIterable) {
// ...
}
spread Operator
// Iterable의 모든 value들이 arr에 추가된다.let arr = [firstElem, ...someIterable, lastElem];
// Iterable의 모든 value들이 arguments에 추가된다. someFunction(firstArg, ...someIterable, lastArg);
Positional Destructing
// Iterable의 첫 3개 값들이 저장된다.let [a, b, c] = someIterable;
Generators
Generator는 Iterable이면서 Iterator인 객체의 특별한 종류이다. 이 객체는 일시정지와 재시작 기능을 여러 반환 포인트들을 통해 사용할 수 있다. 이러한 반환 포인트들은yield키워드를 통해 구현할 수 있으며, 오직 generator 함수에서만 사용할 수 있다.next호출시마다 다음 yield의 expression이 반환된다. yield value를 사용하면 한가지 값을 반환할 수 있고, yield* iterable을 사용하면 해당되는 Iterable의 값들을 순차적으로 반환시킬 수 있다.
Generator의 반복이 끝나는 시점은 3가지 경우인데, generator 함수에서return사용, 에러 발생 그리고 마지막으로 함수의 끝부분까지 모두 수행된 이후, 이렇게 3가지 경우이다. 그리고 이때 done 프로퍼티가 true가 될 것이다.
이러한 generator함수는 Generator객체를 반환하며,function*키워드로 정의할 수 있다. 또는 클래스에서 메서드 이름 앞에*을 붙여 정의할 수도 있다.
A Basic Generator
function*myGenFn() {
yield1;
yield2;
return3;
}
let iterator =myGenFn(),
capture;
console.log('--------------- do-while loop ---------------');
do {
capture =iterator.next();
console.log(capture);
} while (!capture.done);
//또는console.log('\n--------------- for-of loop ---------------');
for (constnofmyGenFn()) {
console.log(n);
}
return문을 사용하지 않았다면, 마지막 next호출에서 value가 3이 아닌 undefined가 반환되었을 것이다.
let fib = {
*[Symbol.iterator]() {
let prev =0, curr =1; // [prev, curr] = [0, 1];yield prev;
yield curr;
while (true) {
let temp = prev;
prev = curr;
curr = temp + curr;
// [prev, curr] = [curr, prev + curr];yield curr;
}
}
};
for (constnof fib) {
if (n >100) break;
console.log(n);
}
Generator Methods
next(value) 이 메서드는 다음 값을 얻는 역할을 하며, Iteraotr의 next메서드와 유사하지만, optional argument를 받는다는 점이 다르다.(첫번째 호출에서는 받지 않고 무시한다.) 이 매개변수는 바로 이전의 yield [expression]의 반환값으로 사용된다. (아래 예시를 참고.)
function*fibonacci() {
var fn1 =1;
var fn2 =1;
while (true) {
var current = fn2;
fn2 = fn1;
fn1 = fn1 + current;
var reset =yield current;
console.log('----> reset', reset);
if (reset) {
fn1 =1;
fn2 =1;
}
}
}
var sequence =fibonacci();
console.log(sequence.next().value); // 1console.log(sequence.next().value); // 1console.log(sequence.next().value); // 2console.log(sequence.next().value); // 3console.log(sequence.next().value); // 5console.log(sequence.next().value); // 8console.log(sequence.next(true).value); // reset = true -> yield 1console.log(sequence.next().value); // 1console.log(sequence.next().value); // 2console.log(sequence.next().value); // 3
return(value) 이 메서드는 매개변수로 온 값을 value로써 반환하고, Generator를 종료시킨다. {value: value, done: true}
throw(exception) 이 메서드는 인자로 받은 에러 발생시키고, Generator를 종료시킨다. generator함수 내부의 catch 구문을 통해 처리할 수도 있다. 또한 내부에서 catch 구문으로 처리한 경우 Generator는 종료되지 않는다.
Summary
Iterator가 훌륭하다면 Generator는 더욱 훌륭하다. for-of loop, spread operator, destructing을 완벽하게 활용하기 위해서 Iterator와 Generator에 대한 이해가 중요하다
varLog4js=require('log4js');// 設定ファイル(log-config.json)の読み込みLog4js.configure('log-config.json');// ログ出力 varsystemLogger=Log4js.getLogger('system');varaccessLogger=Log4js.getLogger('access');varerrorLogger=Log4js.getLogger('error');// Informationログを出力するsystemLogger.info('this is system log!!!');accessLogger.info('this is access log!!!');errorLogger.info('this is error log!!!');
$ ls logs まだファイルは作成されていない
$ node app.js 上記で作成したapp.jsを実行
$ ls logs ログファイルが作成されている
access.log error.log system.log
作成されたログファイルは次の通り。
/logs/system.log
[2014-11-29 01:44:54.322] [INFO] system - this is system log!!!
logs/access.log
[2014-11-29 01:44:54.324] [INFO] access - this is access log!!!
error.log
[2014-11-29 01:44:54.324] [INFO] error - this is error log!!!
varLog4js=require('log4js');// 設定ファイル(log-config.json)の読み込みLog4js.configure('log-config.json');// ログ出力 varsystemLogger=Log4js.getLogger('system');// Fatalログを出力するsystemLogger.fatal('this is system log!!!');// Errorログを出力するsystemLogger.error('this is system log!!!');// Warningログを出力するsystemLogger.warn('this is system log!!!');// Informationログを出力するsystemLogger.info('this is system log!!!');// Debugログを出力するsystemLogger.debug('this is system log!!!');// traceログを出力するsystemLogger.trace('this is system log!!!');
実際に出力するとこんな感じになります。
system.log
[2014-11-29 21:16:34.628] [FATAL] system - this is system log!!!
[2014-11-29 21:16:34.630] [ERROR] system - this is system log!!!
[2014-11-29 21:16:34.630] [WARN] system - this is system log!!!
[2014-11-29 21:16:34.630] [INFO] system - this is system log!!!
[2014-11-29 21:16:34.630] [DEBUG] system - this is system log!!!
[2014-11-29 21:16:34.631] [TRACE] system - this is system log!!!
[2014-11-29 23:12:49.403] [FATAL] system - this is system log!!!
[2014-11-29 23:12:49.406] [ERROR] system - this is system log!!!
[2014-11-29 23:12:49.406] [WARN] system - this is system log!!!
[2014-11-29 23:12:49.407] [INFO] system - this is system log!!!
$ node app.js
[2014-11-29 23:31:05.041] [FATAL] system - this is system log!!!
[2014-11-29 23:31:05.043] [FATAL] access - this is access log!!!
[2014-11-29 23:31:05.043] [FATAL] error - this is error log!!!
[2014-11-29 23:31:05.044] [ERROR] system - this is system log!!!
[2014-11-29 23:31:05.044] [ERROR] access - this is access log!!!
[2014-11-29 23:31:05.044] [ERROR] error - this is error log!!!
[2014-11-29 23:31:05.044] [WARN] system - this is system log!!!
[2014-11-29 23:31:05.044] [WARN] access - this is access log!!!
[2014-11-29 23:31:05.044] [WARN] error - this is error log!!!
[2014-11-29 23:31:05.045] [INFO] system - this is system log!!!
[2014-11-29 23:31:05.045] [INFO] access - this is access log!!!
[2014-11-29 23:31:05.045] [INFO] error - this is error log!!!
[2014-11-29 23:31:05.045] [DEBUG] system - this is system log!!!
[2014-11-29 23:31:05.045] [DEBUG] access - this is access log!!!
[2014-11-29 23:31:05.045] [DEBUG] error - this is error log!!!
[2014-11-29 23:31:05.046] [TRACE] access - this is access log!!!
[2014-11-29 23:31:05.047] [TRACE] error - this is error log!!!
// var는 function-scope이기 때문에 for문이 끝난다음에 i를 호출하면 값이 출력이 잘 된다.// 이건 var가 hoisting이 되었기 때문이다.for(var j=0; j<10; j++) {
console.log('j', j)
}
console.log('after loop j is ', j) // after loop j is 10// 아래의 경우에는 에러가 발생한다.functioncounter () {
for(var i=0; i<10; i++) {
console.log('i', i)
}
}
counter()
console.log('after loop i is', i) // ReferenceError: i is not defined
그럼 항상 function을 만들어서 호출해야 할까? 그건 아니다.
javascript에서는 immediately-invoked function expression (or IIFE, pronounced "iffy")라는것이 있다.
IIFE로 function-scope인거 처럼 만들 수가 있다.
// IIFE를 사용하면// i is not defined가 뜬다.
(function() {
// var 변수는 여기까지 hoisting이 된다.for(var i=0; i<10; i++) {
console.log('i', i)
}
})()
console.log('after loop i is', i) // ReferenceError: i is not defined
근데 javascript는 여기서 좀 웃긴 부분이 있다.
위에서 잠깐 말했지만 IIFE는 function-scope처럼 보이게 만들어주지만 결과가 같지는 않다.
// 이 코드를 실행하면 에러없이 after loop i is 10이 호출된다.
(function() {
for(i=0; i<10; i++) {
console.log('i', i)
}
})()
console.log('after loop i is', i) // after loop i is 10
위에 코드가 아무 에러 없이 실행되는 이유는 i가 hoisting이 되어서 global variable이 되었기 때문이다.
그래서 아래와 같이 된 것이다.
var i
(function() {
for(i=0; i<10; i++) {
console.log('i', i)
}
})()
console.log('after loop i is', i) // after loop i is 10
IIFE는 쓰는데 이렇게 hoisting이 된다면 무슨 소용이 있겠는가?!
그래서 이런 hoisting을 막기 위해 use strict를 사용한다.
// 아까랑 다르게 실행하면 i is not defined라는 에러가 발생한다.
(function() {
'use strict'for(i=0; i<10; i++) {
console.log('i', i)
}
})()
console.log('after loop i is', i) // ReferenceError: i is not defined
어떤가? 뭔가 변수 선언때문에 너무 많은 일을 한다고 생각하지 않는가?
그럼 let, const에 대해서 알아보자.
let, const(block-scoped)
es2015에서는 let, const가 추가 되었다.
javascipt에는 그동안 var만 존재했기 때문에 아래와 같은 문제가 있었다.
// 이미 만들어진 변수이름으로 재선언했는데 아무런 문제가 발생하지 않는다.var a ='test'var a ='test2'// hoisting으로 인해 ReferenceError에러가 안난다.
c ='test'var c
위와 같은 문제점으로 인해 javascript를 욕 하는 사람이 참 많았다.
하지만 let, const를 사용하면 var를 사용할때보다 상당히 이점이 많다.
두개의 공통점은 var와 다르게 변수 재선언 불가능이다.
let과 const의 차이점은 변수의 immutable여부이다.
let은 변수에 재할당이 가능하지만,
const는 변수 재선언, 재할당 모두 불가능하다.
// letlet a ='test'let a ='test2'// Uncaught SyntaxError: Identifier 'a' has already been declared
a ='test3'// 가능// constconstb='test'constb='test2'// Uncaught SyntaxError: Identifier 'a' has already been declared
b ='test3'// Uncaught TypeError:Assignment to constant variable.
let, const가 hoisting이 발생하지 않는건 아니다.
var가 function-scoped로 hoisting이 되었다면
let, const는 block-scoped단위로 hoisting이 일어나는데
c ='test'// ReferenceError: c is not definedlet c
위에 코드에서 ReferenceError가 발생한 이유는 tdz(temporal dead zone)때문이다.
let은 값을 할당하기전에 변수가 선언 되어있어야 하는데 그렇지 않기 때문에 에러가 난다.
이건 const도 마찬가지인데 좀 더 엄격하다.
// let은 선언하고 나중에 값을 할당이 가능하지만let dd
dd ='test'// const 선언과 동시에 값을 할당 해야한다.constaa// Missing initializer in const declaration
이렇게 javascript에 tdz가 필요한 이유는 동적언어이다 보니깐 runtime type check 가 필요해서이다.