댓글 쓰기 권한이 없습니다. 로그인 하시겠습니까?
HTML5
2018.01.04 13:29
[Javascript] Promise 이해하기
조회 수 7209 댓글 0
출처: http://yubylab.tistory.com/entry/%EC%9E%90%EB%B0%94%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8-Promise-%EC%9D%B4%ED%95%B4%ED%95%98%EA%B8%B0 최근들어 Node.js는 물론 많은 수의 Nosql이 자바스크립트를 기반으로 동작을 하고 있습니다. 그러다 보니 이전에 큰 문제가 아니었던 부분들이 이제는 성능상 또는 코드의 유지보수 측면에서 중요한 부분으로 부각이 되고있습니다. 특히 콜백을 다루는 부분은 자바스크립트로 어플리케이션을 만들때 가장 중요한 부분입니다. 단순히 콜백뿐만이 아니라 코드의 전체적인 흐름을 깔끔하게 하기 위해서라도 promise를 이해하고 코드에 적용하는 것이 좋은것같습니다. ...... JavaScripters여러분 이제 promise를 사용하는데 문제가 있음을 인정해야 할 때 입니다. 물론 promise자체에는 아무런 문제가 없습니다. promise는 A+ 스펙으로 굉장한 녀석입니다. 그런데 문제는 이를 사용하는 우리에게 있습니다. javascript기반의 nosql (mongodb, pouchdb)나 promise로 과하게 구현된 API를 사용하는 코드를 보다 보면 그 문제가 더욱 명확해 보입니다. 우리 대다수는 promise를 진짜로 이해하고 사용하고 있는 것 같지않습니다. 이를 믿지 못하겠다면 최근에 제가 트위터에 올린 글을 확인해보세요. Q: What is the difference between these four promises? doSomething().then(function () { return doSomethingElse(); }); doSomething().then(function () { doSomethingElse(); }); doSomething().then(doSomethingElse()); doSomething().then(doSomethingElse); 만약에 위의 차이점에 대해서 제대로 이해를 하고 있다면 당신은 promise를 제대로 이해하고 있습니다. 더 이상 이 글을 읽을 필요가 없습니다. 안녕히 가세요. 그런데 이 네가지의 차이점을 설명하지 못한다면 이 글을 계속 읽으셔야 합니다. 놀랍게도 어느 누구도 이 질문에 대답을 하지 못했습니다. 그리고 특히 세 번째 질문의 답은 저에게도 놀라웠습니다. 제가 문제를 냈는데도 말이지요. 일단 답은 이 글의 마지막 부분에 알려드리겠습니다. 우선 왜 이렇게 promise가 지랄맞고(triky) ,초보자든 경험이 많은 개발자든 왜 이렇게 promise를 사용하는데 실수를 범하는 것인지 알아봐야겠습니다. 그리고 마지막에는 한가지 promise를 제대로 이해할 수 있는 promise에 대한 하나의 통찰을 드리겠습니다. 물론 이 글의 끝 무렵에는 더 이상 promise로 고생하게 되는 일은 없을것입니다. Wherefore promises? promise에 관한 글을 읽게 되면 종종 pyramid 돔 이라는 것을 마주치게 됩니다. 이는 최악의 callback코드들이 꼬리에 꼬리를 물면서 결국 모니터 오른쪽을 뚫고 나갈 듯이 쌓여 있는 형태를 말합니다. promise는 바로 이 문제를 해결하게 위해 존재합니다. 하지만 단순히 들여쓰기의 문제 때문은 아닙니다. "Redemption from Callback Hell" 에 따르면 callback이 가지는 진짜 문제는 우리에게서 return과 throw를 사용할 기회를 빼앗아 간다는 것입니다. 대신에 우리의 프로그램이 한 함수가 우연찮게 다른 함수를 호출하는 방식 등으로 원하지 않게 흘러가는 경우가 발생할 수 있습니다. 그리고 솔직히 callback은 뭔가 불길한 존재입니다. callback은 우리가 보통 프로그래밍을 통해 보장한 stack을 우리에게서 빼앗습니다. 무언가 보장된 stack이 없는 상태로 코드를 짠다는 것은 브레이크 없이 운전을 하는것과 같습니다. 우리는 얼마나 브레이크가 필요한지는 필요한 시점이 돼서야 알기 때문에 그전에는 결코 알기가 어렵습니다. 결국 promise는 우리가 비동기 프로그래밍을 하며 잃었던 return, throw를 돌려받아 언어의 기본으로 돌아가는 것입니다. 그래서 우리는 이 promise가 가진 이익을 사용하기 위해 제대로 이해 할 필요가 있습니다. Rookie mistakes 사람들은 promise를 만화를 통해 이해시키려고 하거나 단어의 의미에 집중해서 “promise는 비동기적으로 얻은 값을 전달하는 역할을 해” 라고 설명을 합니다. 그러나 이런 설명들 중에 정말로 이해를 하는데 도움이 될만한 설명을 찾지 못했습니다. 저에게 promise는 모든 코드의 구조이고 흐름입니다. 그래서 저는 보통 우리가 저지르는 실수를 검토해보고 이를 해결하는 방법을 보여주는 것이 promise를 이해하는데 더 좋은 방법이라고 생각합니다. 잠깐 옆으로 빠져서 “promise”라는 단어는 사람들에게 각기 다른 의미를 가집니다. 하지만 이 글에서는 the official spec 에 나오는 의미의 promise를 의미합니다. 최신브라우저의 window.Promise를 나타내겠지요. 모든 브라우저에서 window.Promise를 가지고 있지는 않습니다. 그래서 이를 지원하기 위해 Lie 라는 이름의 외부 라이브러리를 통해 기본적인 promise를 지원하게 할 수 있습니다. 지금부터는 초보자가 저지르기 쉬운 실수에 대해서 알아보겠습니다. 이 글은 PouchDB의 개발자 블로그에 올라온 글이라 PouchDB를 기반으로 설명을 하고 있지만 그다지 중요한 사항은 아닙니다. 그냥 promise라는 것에만 집중을 해보겠습니다. Rookie mistake #1: the promisey pyramid of doom 사람들이 PouchDB의 promise기반의 API를 사용하는 것을 보시시지요. 매우 좋지 않은 형태의 프로미스 패턴이 남발되고 있습니다. 가장 일반적으로 일으키는 나쁜 실수가 바로 이것입니다. remotedb.allDocs({ include_docs: true, attachments: true }).then(function (result) { var docs = result.rows; docs.forEach(function(element) { localdb.put(element.doc).then(function(response) { alert("Pulled doc with id " + element.doc._id + " and added to local db."); }).catch(function (err) { if (err.status == 409) { localdb.get(element.doc._id).then(function (resp) { localdb.remove(resp._id, resp._rev).then(function (resp) { // et cetera... promise가 마치 콜백만을 다루기 위한 도구로 사용하고 있음을 알 수 있습니다. 만약 위와 같은 실수들이 초보자들만 저지르는 실수라고 생각할 수 있지만 위의 코드는 Black Berry 공식 개발자 블로그에서 가져온 코드입니다. 예전방식의 callback 은 결코 없어질 수 없는 것 같습니다. 위와 같은 방식이 아닌 조금 더 낳은 방식은 이렇습니다. remotedb.allDocs(...).then(function (resultOfAllDocs) { return localdb.put(...); }).then(function (resultOfPut) { return localdb.get(...); }).then(function (resultOfGet) { return localdb.put(...); }).catch(function (err) { console.log(err); }); 이러한 방식을 우리는 composing promise라고 부릅니다. 이 방법은 promise가 가진 매우 강력한 방식 중 하나입니다. 각각의 함수들은 이전의 함수들의 작업이 끝이 나면 그때 다름 작업들을 진행합니다. 매우 정돈되어있고 직관적으로 이해하기도 쉽습니다. Rookie mistake #2: WTF, how do I use forEach() with promises? 이 부분이 대부분의 사람들이 promise를 이해하려 할 때 문제가 생기는 지점입니다. 일반적으로 반복을 다루는 일을 할 때 forEach, for, while등 우리가 기존에 알고 있는 문법을 사용합니다. 그리고 문제는 이 반복문을 promise와 함께 사용하려 할 때 어떤 방식으로 구현을 해야 하는지 모릅니다. 그래서 일단 아래와 같이 코드를 작성합니다. 얼핏 보면 문제없이 동작을 할 것 같습니다. // I want to remove() all docs db.allDocs({include_docs: true}).then(function (result) { result.rows.forEach(function (row) { db.remove(row.doc); }); }).then(function () { // I naively believe all docs have been removed() now! }); 그런데 위의 코드의 문제가 뭘까요? 문제는 첫 번째 함수가 undefined를 리턴하는 것입니다. 이 말은 두 번째 함수가 모든 documents에서 db.remove()를 호출하는 것을 기다리지 않는다는 말입니다. 실제로 몇몇 documents들이 지워지고 아무것도 기다리지 않고 두번째 함수가 실행이 됩니다. 즉, 리턴되는 값이 없다면 해당 작업에 대한 확신을 보장하지 못하게 됩니다. 즉 루프가 돌면서 실행하는 작업의 결과를 return 함으로써 동작의 확신성을 보장해야 한다는 것입니다. 위와 같이 코드를 짜고 다음 then() 메서드에 진입했을 때 우리는 모든documents가 지워졌을 것이라고 생각을 하지만 그렇지 않다는 사실에 당황을 하게 됩니다. PouchDB가 UI의 업데이트 되는 것에 맞춰서 빠르게 documents를 제거한다면 우리는 어떠한 위와 같은 문제를 인지하지 못하게 됩니다. 버그는 아마 특정 조건이나 브라우저에서 발생 할 수 있으며 이런 종류의 버그는 개발자 입장에서는 인지하기 매우 어려운 버그종류입니다. 그래서 위와 같은 작업을 할 때 우리에게 필요한 것은 foreach() , for, while 같은 일반적인 반복문이 아니라 Promise.all() 이라는 메서드를 사용하는 것입니다. db.allDocs({include_docs: true}).then(function (result) { return Promise.all(result.rows.map(function (row) { return db.remove(row.doc); })); }).then(function (arrayOfResults) { // All docs have really been removed() now! }); 무슨 일이 지금 벌어진 걸까요? 기본적으로 Promise.all() 은 promise의 배열을 입력 값으로 받습니다. 이는 promise가 하나하나 resolved될 때 다른 resolved되지 않은 다른 promise를 전달합니다. 이는 비 동기적인 for-loop와 동일합니다. Promise.all() 는 배열의 형태로 다음함수에 결과를 전달합니다. 이는 매우 유용한데 만약에 우리가 다수의 것들을 PouchDB로부터 가져와 get()을 한다고 하면 여러 sub-promise중에 하나라도 거부가 된다면 all() promise도 자연스럽게 거부가 일어나게 될 것입니다. Rookie mistake #3: forgetting to add .catch() 다음 실수는 많은 개발자들이 자신의 코드에는 어떠한 에러도 발생하지 않을 것이라는 자신감으로 .catch() 를 사용하지 않는다는 것입니다. 불행히도 이 말은 에러가 발생해도 드러내지 않고 그냥 조용히 넘어 간다는 말입니다. 이런 에러는 심지어 console에서도 볼 수가 없어서 디버깅을 하는데 어려움이 있습니다. 저는 이런 끔찍한 시나리오를 피하기 위해 promise 체인의 끝에다가 catch()를 붙이는 습관을 가지고 있습니다. somePromise().then(function () { return anotherPromise(); }).then(function () { return yetAnotherPromise(); }).catch(console.log.bind(console)); // <-- this is badass 만약 당신이 어떠한 에러가 발생하지 안는다고 확신해도 붙이세요. 여러분의 확신이 잘못된 것 이라고 판명이 나더라도 이런 습관덕분에 여러분의 삶을 더 아름다워지게 만들어 줄 것입니다. Rookie mistake #4: using "deferred" 이 실수는 항상 발견하는 실수입니다. 간단하게 promise는 긴 스토리와 역사를 가지고 있습니다. 자바스크립트 커뮤니티에서 오랬동안 deffered가 옳은 것인지에 대한 논쟁이 있었고 최근들어 jQuery나 Angular에서 deffered를 사용하기 시작하면서 ES6의 스펙으로 자리를 잡게 되엇습니다. 코드를 작성할 때 생길 문제를 피하는 방법입니다. 우선 대부분의 promise라이브러리는 third-party 라이브러리로부터 promise를 가져다 쓰는 방법을 제공합니다. 예를들어 Angular의 경우에는 $q 모듈은 $q.when()을 사용하여 $q promise가 아닌 경우에 감싸주는 역할을 합니다. 그래서 Angular 사용자라면 PouchDB의 promise를 아래와 같은 방식으로 감쌀수가 있습니다. $q.when(db.put(doc)).then(/* ... */); // <-- this is all the code you need 다른 방법은 revealing constructor pattern을 사용하는 것입니다. 이는 non-promise API를 감싸는 효과적인 방법입니다. 예를들어 Node의 fs.readfile() 이라는 callback기반의 API를 감쌀 때 간단하게 아래의 코드처럼 처리를 하면 됩니다. new Promise(function (resolve, reject) { fs.readFile('myfile.txt', function (err, file) { if (err) { return reject(err); } resolve(file); }); }).then(/* ... */) Rookie mistake #5: using side effects instead of returning 이 코드에 어떤 문제가 있는 것 같나요? somePromise().then(function () { someOtherPromise(); }).then(function () { // Gee, I hope someOtherPromise() has resolved! // Spoiler alert: it hasn't. }); 좋습니다. 이 코드를 가지고 당신이 알아야 하는 promise의 모든 것에 대해서 이야기 할 수 있습니다. 진지하게, 이 방법은 꽤나 이상해 보일 수 있지만 한번 이해하고 난 다음에는 우리가 여태 이야기한 모든 에러들을 예방할 수 있는 매우 좋은 방법입니다. 이전에 말했는데 promise가 가진 최대의 이점은 우리에게 소중한 return과 throw를 되돌려준다는 것입니다. 하지만 실제로 이게 어떤 형태일까요? 모든 promise는 우리에게 then() 메서드를 제공합니다. (또는 catch() 메서드나요.) then() 메서드 내부를 보겠습니다. somePromise().then(function () { // I'm inside a then() function! }); 이 함수의 내부에서 우리는 어떤 일을 할 수가 있을까요? 세가지가 가능합니다. 1. return 다른 promise 2. return 발생한 값 이나 undefined 3, throw 에러 이게 다입니다. 이제 이것들을 이해를 하면 여러분은 promise를 이해한 것입니다. 하나씩 살펴보겠습니다. 1. Return another promise 이는 위에서 살펴본 composing promise의 일반적인 패턴입니다. getUserByName('nolan').then(function (user) { return getUserAccountById(user.id); }).then(function (userAccount) { // I got a user account! }); 첫 번째 함수가 getUserAccountById() 함수를 리턴 합니다, 다음 함수로 말이지요. 다음 메서드는 콜백으로 해당 함수의 리턴값을 받습니다. 여기서 return을 하는 것이 매우 중요한데 만약에 return을 하지 않는다면 다음 함수는 아마 undefined를 받게 될 것입니다. 우리는 userAccount가 필요한데 말입니다. 2. Return a synchronous value (or undefined) undefined를 리턴 하는 일은 종종 발생하는 실수입니다. 하지만 동기적으로 발생한 값을 리턴 하는 것은 동기적인 코드를 promise방식의 코드로 변환하는 굉장한 방법입니다. 예를들어 우리가 사용자 캐시를 in-memory 에 가지고 있다고 하겠습니다. 우리는 아래의 코드처럼 구현할 수 있습니다. getUserByName('nolan').then(function (user) { if (inMemoryCache[user.id]) { return inMemoryCache[user.id]; // returning a synchronous value! } return getUserAccountById(user.id); // returning a promise! }).then(function (userAccount) { // I got a user account! }); 굉장하지 않나요? 두 번째 함수는 userAccount가 동기적이나 비동기적으로 가져온 값인지 아닌지는 신경 쓰지 않습니다. 첫 번째 함수는 동기적인 값이나 비 동기적인 값 모두를 마음대로 전달할 수가 있습니다. 불행하게도 값을 리턴하지 않는 함수의 경우에는 자바스크립트는 undefined로 값을 처리해버립니다. 이는 예상치 못한 문제를 일으키게 됩니다. 이러한 이유로 저는 개인적으로는 then() 함수 안에서는 반드시 throw나 return을 통해서 무언가를 다음 함수로 전달을 시킵니다. 여러분도 이렇게 해야 합니다. 3. Throw a synchronous error throw는 promise를 더욱 굉장하게 만들어 주는 역할을 합니다. 동기적으로 에러가 발생하면 throw를 통해 에러를 전달합니다. 사용자가 로그아웃이 되었다면 에러를 발생시키는 코드입니다. getUserByName('nolan').then(function (user) { if (user.isLoggedOut()) { throw new Error('user logged out!'); // throwing a synchronous error! } if (inMemoryCache[user.id]) { return inMemoryCache[user.id]; // returning a synchronous value! } return getUserAccountById(user.id); // returning a promise! }).then(function (userAccount) { // I got a user account! }).catch(function (err) { // Boo, I got an error! }); 우리의 catch()는 사용자가 로그아웃이 되었다면 동기적으로 에러를 받습니다. 그리고 물론 promise가 reject가 발생하는 경우 비동기적으로 에러를 받습니다. 다시 한번 강조하면 함수는 에러가 동기적이든 비동기적이든 전혀 신경 쓰지 않습니다. 이는 매우 유용한데 왜냐하면 개발 시에 코딩의 에러를 발견하는데 도움을 줍니다. 예를 들어 then() 메서드의 내부에 JSON.parse()를 한다고 가정 하겠습니다. 이 경우에 json에 문제가 있다면 동기적으로 에러를 발생시킬 수 있습니다. callback의 경우에는 에러를 무시하는 일이 발생합니다. 하지만 promise와 함께라면 우리는 간단하게 catch() 내부에서 이모든 일들을 처리할 수 있습니다. Advanced mistakes 자, 이제 조금 더 깊이 있게 들어가보겠습니다. 이번에 다룰 내용들은 advanced 내용입니다. advanced로 분류가 된 이유는 이미 promise를 이해하고 받아들인 개발자들이 일으키는 실수이기 때문입니다. 하지만 우리는 이것들을 다룰 필요가 있습니다. 만약에 우리가 쉽게 해결할 수 있는 문제였다면 앞으로의 내용들을 beginner 섹션에 다뤘을 것입니다. Advanced mistake #1: not knowing aboutPromise.resolve() 위의 다양한 예들을 통해 promise는 동기적인 코드를 비 동기적인 코드로 감싸주는 매우 유용한 기능입니다. 구현은 아래와 같이 합니다. new Promise(function (resolve, reject) { resolve(someSynchronousValue); }).then(/* ... */); 위의 표현을 Promise.resolve()를 통해 훨씬 간결하게 표현하는 것이 가능합니다. Promise.resolve(someSynchronousValue).then(/* ... */); 이 방법은 어떠한 동기적인 에러를 잡는데 매우 유용합니다. 그래서 저는 대부분의 promise-returning API를 구현할 때 제일 첫 시점에 아래와 같이 형태를 잡아두고 시작을 합니다. function somePromiseAPI() { return Promise.resolve().then(function () { doSomethingThatMayThrow(); return 'foo'; }).then(/* ... */); } 그냥 기억하세요. 동기적으로 throw가 발생하는 코드는 코드상의 어딘가에서 에러가 묻혀버려 거의 디버깅이 불가능합니다. 하지만 만약 모든 것들을 Promise.resolve()로 감싼다면 우린 항상 이것들일 catch()를 통해 확인할 수가 있습니다. 이와 유사하게 Promise.reject()가 있는데 이는 당장 promise 를 거부시키는데 사용할수있습니다. Promise.reject(new Error('some awful error')); Advanced mistake #2: catch() isn't exactly likethen(null, ...) 저는 위에서 catch()는 매우 유용하다고 말했습니다. 그래서 아래의 두 개의 작은 코드는 동일합니다. somePromise().catch(function (err) { // handle error }); somePromise().then(null, function (err) { // handle error }); 하지만 아래의 두 개의 코드는 동일한 의미는 아닙니다. somePromise().then(function () { return someOtherPromise(); }).catch(function (err) { // handle error }); somePromise().then(function () { return someOtherPromise(); }, function (err) { // handle error }); 만약에 위의 두 코드가 왜 다른지 궁금하다면 첫번째 코드에서 첫 함수가 에러를 반환한다면 무슨 일이 벌어질지를 생각해보면 됩니다. somePromise().then(function () { throw new Error('oh noes'); }).catch(function (err) { // I caught your error! :) }); somePromise().then(function () { throw new Error('oh noes'); }, function (err) { // I didn't catch your error! :( }); 보시는 봐와 같이 then(resolveHandler, rejectHandler)의 형태로 구현을 한다면 에러가 resolveHandler를 통해 반환이 된경우라면 rejectHandler가 에러를 잡아내지 못하게 됩니다. 이러한 이유로 저는 절대 두번째 방식으로는 구현을 하지 않습니다. 항상 에러에 대비해서는 catch()를 사용합니다. 단, Mocha 를 통한 비동기적인 테스트 코드 구현은 제외합니다. it('should throw an error', function () { return doSomethingThatThrows().then(function () { throw new Error('I expected an error!'); }, function (err) { should.exist(err); }); }); Advanced mistake #3: promises vs promise factories 여러분이 순차적으로 하나씩 실행이되는 promise를 구현하는 경우에 대해서 이야기 해보겠습니다. 우리 하나씩 순차적으로 실행이 되는 Promise.all() 같은 것을 원할것입니다. 아마 여러분은 단순히 아래처럼 코드를 작성할것입니다. function executeSequentially(promises) { var result = Promise.resolve(); promises.forEach(function (promise) { result = result.then(promise); }); return result; } 불행하게도 위의 코드는 여러분이 기대한 동작대로 동작을 하지 않을것입니다. executeSequentialy()로 전달이 되는 promise의 경우에는 병렬적으로 동작을 할 것입니다. 이러한 일이 벌어지는 이유는 우리의 동작들이 promise의 배열을 무시하고 동작하기를 원하지 않기 때문입니다. 하나의 promise가 하나씩 생성이 되고 동작이 되는 방식을 원합니다. 그래서 우리가 정말로 원하는 것은 promise factory의 배열입니다. function executeSequentially(promiseFactories) { var result = Promise.resolve(); promiseFactories.forEach(function (promiseFactory) { result = result.then(promiseFactory); }); return result; } 여러분이 무슨생각을 하고 있을지 압니다, “누가 자바프로그래머지? 왜 갑자기 factory에 대한 이야기를 꺼내는거지?” 라고 말입니다. 실제로 factory는 매우 간단합니다. 이는 간단히 promise를 리턴하는 함수일 뿐입니다. function myPromiseFactory() { return somethingThatCreatesAPromise(); } 왜 이게 동작을 하냐고요? 이건 매우 잘 동작합니다. promise factory는 promise를 요청하기전까지는 생성하지 않습니다. 이는 then 함수와 동일하게 동작을 합니다. 실제로 이 둘은 같습니다. 만약에 여러분이 executeSequentially() 함수를 들여다 본다면 myPromisefactory가 result.then(…)의 내부에서 대신 기능을 수행하고 있다고 상상해보세요. 그러면 뭔가가 promise에 대한 깨닳음을 얻을 수가 있지 않을까 생각합니다. Advanced mistake #4: okay, what if I want the result of two promises? 종종 하나의 promise가 다른 것에 의존하는 경우가 있습니다. 하지만 우리는 그 두개의 promise를 결과물로 얻고싶은 경우가 있습니다. 아래와 같은 경우입니다. getUserByName('nolan').then(function (user) { return getUserAccountById(user.id); }).then(function (userAccount) { // dangit, I need the "user" object too! }); 좋은 자바스크립트 개발자가 되고 싶다면 피라미드 형식으로 코드를 짜는 것을 피해야 합니다. 우리는 user라는 변수를 최상위 스코프에 정의했습니다. var user; getUserByName('nolan').then(function (result) { user = result; return getUserAccountById(user.id); }).then(function (userAccount) { // okay, I have both the "user" and the "userAccount" }); 이 방식으로도 충분히 잘 동작합니다. 하지만 개인적으로 사용하기엔 조금 불편한 부분을 찾았습니다. 제가 추천하는 방법은 우리가 가진 선입견을 살짝 거두고 피라미드 형태의 코드를 살짝 받아들여 보겠습니다. getUserByName('nolan').then(function (user) { return getUserAccountById(user.id).then(function (userAccount) { // okay, I have both the "user" and the "userAccount" }); }); 아주 일시적으로만요.. 만약에 들여쓰기가 문제가가 된다면 예전부터 자바스크립트 개발자들이 했던것처럼 기능을 함수로 만들어 사용하는 방법을 쓰면 됩니다. function onGetUserAndUserAccount(user, userAccount) { return doSomething(user, userAccount); } function onGetUser(user) { return getUserAccountById(user.id).then(function (userAccount) { return onGetUserAndUserAccount(user, userAccount); }); } getUserByName('nolan') .then(onGetUser) .then(function () { // at this point, doSomething() is done, and we are back to indentation 0 }); 여러분이 만드는 promise가 점점 복잡해간다면 당신은 기능들을 함수로 점점더 많이 분리시키는 자신을 발견할수 있습니다. 저는 이런 형태에서 미학적으로 아름다움을 발견하였습니다. 아래와 같은 코드입니다. putYourRightFootIn() .then(putYourRightFootOut) .then(putYourRightFootIn) .then(shakeItAllAbout); Advanced mistake #5: promises fall through 마지막으로 이번 실수는 매우 소수만의 사람들이 알고있고 아마 여러분의 코드에서는 절대 나오지 않을법한 코드이지만 한번 보겠습니다. 아래와 같은 코드는 어떤 결과를 보여줄까요? Promise.resolve('foo').then(Promise.resolve('bar')).then(function (result) { console.log(result); }); 만약에 여러분이 bar라는 결과가 나올것이라고 생각한다면 틀렸습니다. 실제로는 fool이 나옵니다. 왜 이런 일이 발생하게 되냐면 then() 함수에 함수의 형태가 아닌 것을 전달하기 때문입니다. 이는 실제로 then(null) 이라는 형태가 됩니다. 결국 이전의 promise의 결과가 제대로 나오지 않기 때문에 벌어지는 일입니다. Promise.resolve('foo').then(null).then(function (result) { console.log(result); }); 위의 코드에 then(null) 을 계속 추가 한다고 해도 여전히 결과는 foo가 나올것입니다. 이것은 이전에 알려드린 promise vs promise factories 를 다룬부분으로 돌아가야합니다. 짧게 설명을 하면 우리는 then() 메서드로 곧장 promise를 전달 할수있습니다. 하지만 이는 우리가 원하는대로 동작하지 않습니다. then()은 함수를 받기때문입니다. 아마 우리가 의도하는데로 하기위해서는 아래처럼 코드를 만들어야 할것입니다. Promise.resolve('foo').then(function () { return Promise.resolve('bar'); }).then(function (result) { console.log(result); }); 이제는 bar를 결과로 리턴합니다. 항상 명심하세요 then()에는 함수형태로 전달이 되어야 한다는 것을!! Solving the puzzle 자~ 이제 우리는 promise에 대해서 모든것들을 배웠습니다. 그럼 이제 제가 promise에 대한 설명을 시작하게 끔 만든 그 퀴즈들에 대한 답을 할수 있어여 하겠죠? 이제 답을 알려드리겠습니다. Puzzle #1 doSomething().then(function () { return doSomethingElse(); }).then(finalHandler); doSomething |-----------------| doSomethingElse(undefined) |------------------| finalHandler(resultOfDoSomethingElse) |------------------| Puzzle #2 doSomething().then(function () { doSomethingElse(); }).then(finalHandler); Answer: doSomething |-----------------| doSomethingElse(undefined) |------------------| finalHandler(undefined) |------------------| Puzzle #3 doSomething().then(doSomethingElse()) .then(finalHandler); Answer: doSomething |-----------------| doSomethingElse(undefined) |---------------------------------| finalHandler(resultOfDoSomething) |------------------| Puzzle #4 doSomething().then(doSomethingElse) .then(finalHandler); Answer: doSomething |-----------------| doSomethingElse(resultOfDoSomething) |------------------| finalHandler(resultOfDoSomethingElse) |------------------| 만약 이 정답들이 여전히 이해가 되지 않는다면 다시 이 글을 처음부터 천천히 읽어보시기 바랍니다. 또는 doSomthing(), doSomethingElse() 메서드를 정의해서 브라우저에서 테스트 해보시기 바랍니다. poromise는 굉장한 스펙입니다. 여전이 callback을 사용하고 있다면 이제는 promise로 바꾸기를 강력하게 권장합니다. 여러분의 코드는 더 작아지고, 품격있고 쉬워질것입니다. 만약에 여러분이 저를 믿지 못하신다면 여기 증거가 있습니다. : a refactor of PouchDB's map/reduce module 는 callback을 promise로 변경한 사례입니다. 결과로는 290개의 추가와 555개의 삭제가 있었습니다. 분명 promise는 우리를 자바스크립트 콜백 지옥으로부터 구해줄 구세주 역활을 해줄수 있는 존재입니다. 충분히 promise를 이해하고 앞으로의 코드들 또는 리팩토링을 진행할 코드들에 하나하나 적용을 해나간다면 분명 여러분의 코드가 품격있고 깔끔하면 효율적이 되지 않을까 생각합니다. Dreamy의 코드 스크랩내가 모으고 내가 보는
| ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Designed by sketchbooks.co.kr / sketchbook5 board skin
Sketchbook5, 스케치북5
Sketchbook5, 스케치북5
Sketchbook5, 스케치북5
Sketchbook5, 스케치북5