이전에 async , await으로 비동기 처리를 해봤지만 Promise 개념은 접해보지 못했다. 그래서 개념이해하는 데 어려움을 겪었다. Promise 내부 구조를 모르니 답답했다. 대체 Promise 생성자에서 콜백에 들어가는 resolve, reject는 무엇이고 then 뒤부터는 resolve, reject는 안 쓰는 것인지?
파일로드, 서버로 부터 데이터 요청과 같은 작업은 긴 시간이 소요된다. 그동안 화면이 정지 상태가 된다면 사용자 경험이 좋지 못하기 때문에 비동기 처리가 필요해졌다.
- 비동기 : 한 작업이 끝나기 전에 다음 작업 수행O, 요청만 동기적으로 받고 실제 처리는 비동기적으로 수행
- 동기 : 한 작업이 끝나기 전까지 다음 작업 수행X
순서대로 처리해야 하거나 앞선 작업이 다음 작업에 영향을 미치는 작업의 경우 콜백처리를 했다. 문제는 그 과정이 길어질수록 코드가 복잡해지고 가독성이 떨어져 유지보수가 어렵다는 것. 그래서 Promise 패턴이 이 문제를 해결했다.
Promise 패턴은 이전 작업과 다음 작업을 then으로 연결한다. Promise 생성자에 처음 비동기 처리할 경우 callback은 다음과 같은 형태로 구성해야 한다.
new Promise((resolve,reject)=>{
if(비동기 처리가 success){
resolve(); //then의 첫 번째 파라미터 handleSuccess 콜백함수와 연결
// resolve(then의 첫 번째 파라미터 handleSuccess 콜백함수에 인자로 넘겨줄 값);
}
if(비동기 처리가 fail){
reject(); // then의 두 번째 파라미터 handleError콜백함수 혹은 catch와 연결
//reject(then의 두번째 파라미터 handleError콜백함수의 인자로 넘겨줄 값);
}
})
Promise그 이후는 then으로 연결하여 처리한다.
- then은 두 가지 파라미터를 가진다. 첫 번재 파라미터는 앞선 비동기 처리가 성공했을 경우 다음 처리를 진행하기 위한 콜백함수, 두 번째 파라미터는 앞선 비동기 처리가 실패했을 경우 에러를 처리하기 위한 콜백함수다.
then(handleSuccess, handleError)
- then 부터는 return 이 resolve역할을 대신하고 throw가 reject역할을 대신한다. 주로 then의 첫 번째 파라미터만 사용하고 오류는 catch로 넘긴다.
- then의 두 번째 파라미터는 이전 비동기 처리의 오류만 잡기 때문에 첫 번째 파라미터의 오류를 잡기 위해선 다음 then에 역할을 넘겨야 한다. 코드도 길어지고 복잡해지기 때문에 catch로 모든 비동기 오류나 그 외 오류를 처리하는 것이 좋다.
- then의 두 번째 파라미터로 에러를 처리했으면 catch로 넘어가지 않는다.
new Promise((resolve,reject)=> {
reject('거절당했오.');
})
.then(()=>{},(error)=>{
console.log('then 에서 : '+ error);
})
.catch(err=>{
console.log('catch에서 : ' + err);
});
// then 에서 : 거절당했오.
then으로 chaining하는 방식은 순서대로 처리하기 때문에 시간은 동기적으로 처리하는 것처럼 시간이 오래 걸린다. 순서도 보장하면서 비동기처리의 장점(비동기 작업들 중 가장 오래걸리는 시간 안에 모든 작업이 처리)을 살리는 방법으로 Promise.all이 있다. 여러 비동기 작업들(Promise들)이 들어있는 배열을 인자로 넣고 then의 첫 번째 파라미터의 인자로 여러 비동기 작업들의 결과를 한꺼번에 받는다.
function runPromise(word,wait){
return new Promise((resolve,reject)=>{
setTimeout(()=>{resolve(word);},wait);
});
}
runPromise('hello',2000)
.then(result => {
console.log(result);
return runPromise('world',1000);
})
.then(result => {
console.log(result);
}); // 총 3초가 걸린다.
Promise.all([runPromise('hello',2000),runPromise('world',1000)])
.then(results=> {
results.forEach(result => console.log(result));
}); // 총 2초가 걸린다.
곧장 과제를 진행하기 전에 Promise가 어떻게 생겨먹었을 지, 그리고 어떻게 동작할 지 Promise 클래스를 짜보면서 이해하려고 했다. 대략 (1)Promise생성자에서 인자로 넘겨준 콜백을 바로 실행시킨다는 것, (2)Promise는 pending 상태에서 fulfilled나 rejected에서 넘어가면 새로운 Promise를 반환한다는 말을 듣고 아래와 같이 추측해서 코드를 작성해봤다.
class MyPromise{
static #value
static resolve(value){
MyPromise.#value = value;
return new MyPromise();
}
constructor(callback){
if(!callback){
return;
}
return callback(MyPromise.resolve);
}
then(handleSuccess){
MyPromise.#value = handleSuccess(MyPromise.#value);
return new MyPromise();
}
}
new MyPromise((resolve) => {
resolve('hello');
})
.then((result)=>{
console.log(result);
}); // hello
// static은 MyPromise 함수객체의 프로퍼티 지정, #은 접근자를 private로 지정 (stage 3단계)
실제 구현과 많이 다를 테지만 대략 어떤 식으로 chaining을 하는 건지 알게 된 계기가 되었다.
큰 그림을 그려서 세부사항을 보는 걸 좋아하기 때문에 배운 내용을 토대로 전체 맵을 짜봤다. 디테일한 부분은 이 그림을 보면서 암기할 생각이다. 면접질문 단골 주제라니 확실히 해둬야겠다.
클라이언트가 서버로부터 받는 데이터를 무조건 정상적인 것으로 신뢰해서 생기는 이슈. 서버가 아닌 다른 클라이언트에 타격을 준다. 이전에 innerHtml을 사용하면 XSS 공격에 취약하다는 소리를 들었지만 잘 와닿지 않았다. 이제야 어떤 식으로 공격받는지 눈으로 확인할 수 있어서 좋았다. 해킹 당했는데 재밌다???ㅋㅋㅋ
- 대처 방법으로 innerHtml 대신 textContent사용하기, 혹은 String.prototype.replace 사용하기
- 정규식(RegExp) 조금 공부해봤는데 규칙들이 너무 많아서 외우는 건 포기하고 필요할 때마다 보면서 작성하기로 했다. 이메일 형식이 올바른지 판별할 때와 같이 유용하게 쓰일 것 같다.
XSS와 반대로 서버가 인증받은 클라이언트라면 무조건 신뢰를 해서 생기는 이슈. 자동로그인을 한 채로 악성 사이트에 들어갔을 때 인증정보를 가로채 마치 인증받은 클라이언트인 것처럼 서버에 악성 데이터를 전송할 수 있다. 아직까진 이 부분을 서버에서 완전히 대처하지 못하므로 자동로그인을 설정하지 않거나 시크릿모드로 웹서핑을 하는 등 유저가 조심해야 할 부분이라 한다.
- 다른 오리진에서 자원을 요청할 때 OPTIONS 메서드를 통해 자원 요청 권한을 사전에 검사(preflight) 후 적합하다고 판단이 되면 그때서야 실제 요청이 처리되고 응답받는 과정
- CORS와 관련된 headers
Access-control-allow-headers // 허용되는 headers
Access-control-allow-methods // 허용되는 CRUD verb
Access-control-allow-origin // 허용되는 도메인주소들
공식 문서를 읽고도 이해가 안가서 혼났다. 특히나 OPTIONS를 어떻게 처리해야하는지 막막했다. 그나마 알아낸 것들이 아래와 같다.
- http.createServer(callback) : 서버 생성과 동시에 요청이 올때마다 호출할 callback 지정
- callback의 인자로 request객체와 response객체가 넘겨진다.
- request객체 안에 url, method, headers에 접근할 수 있다.
- request객체의 메서드 on : 이벤트 리스너, 타입은 error/data/end
- data 이벤트에서 바이너리 데이터를 가져온다. 데이터가 한 번에 들어오지 않기 때문에 Buffer에 담아둔다. (비동기 처리)
- end 이벤트에서 모든 데이터를 전송받았음을 보장하기 때문에 여기서 response를 처리해야 한다.
- url 값에 따라 분기점(라우팅)을 둘 수 있다.
- response객체 메서드 writeHead : 파라미터로 status code, headers를 지정
- response객체 메서드 end : 파라미터로 body 지정
- 생성한 server에 listen 메소드를 사용하여 server를 실행
- listen메소드의 파라미터: port, ip, log를 보여줄 callback