Implementing Promises In JavaScript - Lee-hyuna/33-js-concepts-kr GitHub Wiki
ํ๋ก๊ทธ๋๋ฐ์ ๋ํด ๋ด๊ฐ ๊ฐ์ฅ ์ข์ํ๋ ๊ฒ์ aha(์~)์
๋๋ค! ๊ฐ๋
์ ์์ ํ ์ดํดํ๊ธฐ ์์ํ๋ ์๊ฐ์
๋๋ค.
๋น๋ก ๊ฑฐ๊ธฐ๊น์ง ๊ฐ๋๋ฐ ์ค๋ ์๊ฐ์ด ๊ฑธ๋ฆฌ๊ณ ์์ ๋
ธ๋ ฅ๋ ์์์ง๋ผ๋, ๊ทธ๊ฒ์ ํ์คํ ๊ฐ์น๊ฐ ์์ต๋๋ค.
์ฃผ์ด์ง ์ฃผ์ ์ ๋ํ ์ฐ๋ฆฌ์ ์ดํด ์ ๋๋ฅผ ํ๊ฐํ๊ณ ๊ฐ์ ํ๋๋ฐ ๊ฐ์ฅ ํจ๊ณผ์ ์ธ ๋ฐฉ๋ฒ์ ์ง์์ ํ์ค ์ธ๊ณ์ ์ ์ฉํ๋ ค๊ณ ์๋ํ๋ ๊ฒ์ ๋๋ค. ์ด๋ฅผ ํตํด ์ฝ์ ์ ํ์ ํ๊ณ ๊ถ๊ทน์ ์ผ๋ก ํด๊ฒฐํ ์์์๋ฟ๋ง ์๋๋ผ ์๋ ๋ฐฉ์์ ๋ํ ์ ๋ณด๋ ์ป์ ์ ์์ต๋๋ค. ๊ฐ๋จํ ์ํ ์ฐฉ์ค ์ ๊ทผ๋ฒ์ ์ข ์ข ์ด์ ์ ์ ๋งคํ๋ ์ธ๋ถ ์ฌํญ์ ๋๋ฌ๋ ๋๋ค.
์ด๋ฅผ ์ผ๋์ ๋๊ณ Promises๋ฅผ ๊ตฌํํ๋ ๋ฐฉ๋ฒ์ ๋ฐฐ์ฐ๋ ๊ฒ์ด ํ๋ก๊ทธ๋๋ฐ ๊ณผ์ ์์ ๊ฐ์ฅ ์ค์ํ ์๊ฐ ์ค ํ๋๋ผ๊ณ ์๊ฐํฉ๋๋ค. ๋น๋๊ธฐ ์ฝ๋์ ์๋ ๋ฐฉ์์ ๋ํ ๊ท์คํ ํต์ฐฐ๋ ฅ์ ์ป์๊ณ ์ ๋ฐ์ ์ธ ํ๋ก๊ทธ๋๋จธ๊ฐ๋์์ต๋๋ค.
์ด ๊ธ์ด ์๋ฐ์คํฌ๋ฆฝํธ์์ Promises๋ฅผ ๊ตฌํํ๋๋ฐ ๋์์ด๋๊ธฐ๋ฅผ ๋ฐ๋๋๋ค.
Bluebird API์ ๋ช๊ฐ์ง ๋ฐฉ๋ฒ์ผ๋ก Promises / A+ ์ฌ์์ ๋ฐ๋ผ Promise core๋ฅผ ๊ตฌํํ๋ ๋ฐฉ๋ฒ์ ์ค์ ์ ๋ ๊ฒ์
๋๋ค.
๋ํ Jest์ ํจ๊ป TDD๋ฐฉ์์ ์ฌ์ฉํ ๊ฒ์
๋๋ค.
TypeScript๋ ์ ์ฉ ํ ๊ฒ์ ๋๋ค.
์ฌ๊ธฐ์ ์ฐ๋ฆฌ๊ฐ ๊ตฌํ ๊ธฐ์ ์ ์ฐ๊ตฌํ๊ณ ์๋ค๋ ์ ์ ๊ฐ์ํ ๋, Promises๊ฐ ๋ฌด์์ธ์ง์ ๋ํ ๊ธฐ๋ณธ์ ์ธ ์ดํด์ ์๋ ๋ฐฉ์์ ๋ํ ๋ง์ฐํ ์๊ฐ์ ๊ฐ์ง๊ณ ์๋ค๊ณ ๊ฐ์ ํ ๊ฒ์
๋๋ค. ๊ทธ๋ ์ง ์๋ค๋ฉด, ์ฌ๊ธฐ ์์ํ๊ธฐ์ ์ข์ ๊ณณ์ด ์์ต๋๋ค.
์ด์ ๊ทธ ๋ฌธ์ ๋ ์ ์ด๋๊ณ , repository๋ฅผ ๋ณต์ ํ๊ณ ์์ํฉ์๋ค.
์์๋ค์ํผ Promise๋ ๋ค์๊ณผ ๊ฐ์ ์์ฑ์ ๊ฐ์ง ๊ฐ์ฒด์ ๋๋ค.
Promise์ ํธ๋ค๋ฌ๋ฅผ ์ฐ๊ฒฐํ๋ ๋ฐฉ๋ฒ์ ๋๋ค. ํธ๋ค๋ฌ ๋ฉ์๋ ์ค ํ๋์ ์ํด ๋งตํ๋ ์ด์ Promise์ ๊ฐ์ผ๋ก ์๋ก์ด Promise๋ฅผ ๋ฆฌํดํฉ๋๋ค.
then
์ ์ํด ์ฐ๊ฒฐ๋ ํธ๋ค๋ฌ ๋ฐฐ์ด์
๋๋ค. ํธ๋ค๋ฌ๋ onSuccess
์ onFail
์ ๋ ๊ฐ์ง ๋ฉ์๋๋ฅผ ํฌํจํ๋ ๊ฐ์ฒด์ด๋ฉฐ, ๋ ๋คthen (onSuccess, onFail)
์ ์ธ์๋ก ์ ๋ฌ๋ฉ๋๋ค.
type HandlerOnSuccess<T, U = any> = (value: T) => U | Thenable<U>;
type HandlerOnFail<U = any> = (reason: any) => U | Thenable<U>;
interface Handler<T, U> {
onSuccess: HandlerOnSuccess<T, U>;
onFail: HandlerOnFail<U>;
}
Promise๋ resolved, rejected, ํน์ pending ์ธ๊ฐ์ง state์ค ํ๋์ผ ์ ์์ต๋๋ค.
Resolved๋ ๋ชจ๋ ๊ฒ์ด ์์กฐ๋กญ๊ฒ ์งํ๋์ด ๊ฐ์ ์ป๊ฑฐ๋ ์ค๋ฅ๋ฅผ ํ์ ํ๊ณ ์ฒ๋ฆฌํ์์ ์๋ฏธํฉ๋๋ค.
Rejected๋ Promise๋ฅผ ๊ฑฐ๋ถํ๊ฑฐ๋ ์ค๋ฅ๊ฐ ๋ฐ์ํ์ฌ ์ก์ง ์์์์ ์๋ฏธํฉ๋๋ค.
Pending์ resolve ๋๋ reject ๋ฉ์๋๊ฐ ์์ง ํธ์ถ๋์ง ์์์ผ๋ฉฐ ์ฌ์ ํ ๊ฐ์ ๊ธฐ๋ค๋ฆฌ๊ณ ์์์ ์๋ฏธํฉ๋๋ค.
"Promise๊ฐ ํ์ ๋์์ต๋๋ค(the promise is settled)"๋ผ๋ ์ฉ์ด๋ Promise๊ฐ resolved์ด๊ฑฐ๋ rejected๋์์์ ์๋ฏธํฉ๋๋ค.
resolved ํน์ rejected ๊ฐ์
๋๋ค.
๊ฐ์ด ์ค์ ๋๋ฉด ๋ณ๊ฒฝํ ๋ฐฉ๋ฒ์ด ์์ต๋๋ค.
TDD ์ ๊ทผ๋ฒ์ ๋ฐ๋ฅด๋ฉด ์ค์ ์ฝ๋๊ฐ ๋์ค๊ธฐ ์ ์ ํ ์คํธ๋ฅผ ์์ฑํ๋ ค๊ณ ํฉ๋๋ค. ๊ทธ๋ฅ ํด๋ด ์๋ค.
ํต์ฌ ํ ์คํธ๋ ๋ค์๊ณผ ๊ฐ์ต๋๋ค.
describe('PQ <constructor>', () => {
test('resolves like a promise', () => {
return new PQ<number>((resolve) => {
setTimeout(() => {
resolve(1);
}, 30);
}).then((val) => {
expect(val).toBe(1);
});
});
test('is always asynchronous', () => {
const p = new PQ((resolve) => resolve(5));
expect((p as any).value).not.toBe(5);
});
test('resolves with the expected value', () => {
return new PQ<number>((resolve) => resolve(30)).then((val) => {
expect(val).toBe(30);
});
});
test('resolves a thenable before calling then', () => {
return new PQ<number>((resolve) =>
resolve(new PQ((resolve) => resolve(30))),
).then((val) => expect(val).toBe(30));
});
test('catches errors (reject)', () => {
const error = new Error('Hello there');
return new PQ((resolve, reject) => {
return reject(error);
}).catch((err: Error) => {
expect(err).toBe(error);
});
});
test('catches errors (throw)', () => {
const error = new Error('General Kenobi!');
return new PQ(() => {
throw error;
}).catch((err) => {
expect(err).toBe(error);
});
});
test('is not mutable - then returns a new promise', () => {
const start = new PQ<number>((resolve) => resolve(20));
return PQ.all([
start
.then((val) => {
expect(val).toBe(20);
return 30;
})
.then((val) => expect(val).toBe(30)),
start.then((val) => expect(val).toBe(20)),
]);
});
});
Visual Studio Code์ฉ Jest ํ์ฅ์ ์ฌ์ฉํ๋ ๊ฒ์ด ์ข์ต๋๋ค. ๊ทธ๊ฒ์ ์ฐ๋ฆฌ๋ฅผ ์ํด ๋ฐฑ๊ทธ๋ผ์ด๋์์ ํ ์คํธ๋ฅผ ์คํํ๊ณ ์ฝ๋ ์ค ์ฌ์ด์ ๊ฒฐ๊ณผ๋ฅผ ๊ฐ๊ฐ ํต๊ณผ ๋ฐ ์คํจ ํ ์คํธ์ ๋ํ ๋ น์ ๋ฐ ๋นจ๊ฐ์ ์ ์ผ๋ก ํ์ํด์ค๋๋ค.
๊ฒฐ๊ณผ๋ฅผ ๋ณด๋ ค๋ฉด "Output"์ฝ์์ ์ด๊ณ "Jest"ํญ์ ์ ํํ์ญ์์ค.
์ด๋ฏธ์ง ์ฃผ์ : //cdn-media-1.freecodecamp.org/images/0*dr7riPl5ZRkUF8lo๋ค์ ๋ช ๋ น์ ์คํํ์ฌ ํ ์คํธ๋ฅผ ์คํํ ์๋ ์์ต๋๋ค.
npm run test
ํ
์คํธ๋ฅผ ์ํํ๋ ๋ฐฉ๋ฒ์ ๊ด๊ณ์์ด ๋ชจ๋ ํ
์คํธ๊ฐ ๋ถ์ ์ ์ผ๋ก ๋์ ์ค๋ ๊ฒ์ ๋ณผ ์ ์์ต๋๋ค.
๋ณ๊ฒฝํด ๋ด
์๋ค.
class PQ<T> {
private state: States = States.PENDING;
private handlers: Handler<T, any>[] = [];
private value: T | any;
public static errors = errors;
public constructor(callback: (resolve: Resolve<T>, reject: Reject) => void) {
try {
callback(this.resolve, this.reject);
} catch (e) {
this.reject(e);
}
}
}
constructor๋ ๋งค๊ฐ ๋ณ์๋ก callback
์ ๋ฐ์ต๋๋ค.
this.resolve
๋ฐ this.reject
๋ฅผ ์ธ์๋ก ์ฌ์ฉํ์ฌ ์ด callback์ ํธ์ถํฉ๋๋ค.
์ผ๋ฐ์ ์ผ๋ก this.resolve
์ this.reject
๋ฅผ this
์ ๋ฐ์ธ๋ฉํ์ง๋ง ์ฌ๊ธฐ์๋ class arrow ๋ฉ์๋๋ฅผ ๋์ ์ฌ์ฉํ์ต๋๋ค.
์ด์ ๊ฒฐ๊ณผ๋ฅผ ์ค์ ํด์ผํฉ๋๋ค. ๊ฒฐ๊ณผ๋ฅผ ์ฌ๋ฐ๋ฅด๊ฒ ์ฒ๋ฆฌํด์ผํ๋ค๋ ๊ฒ์ ๊ธฐ์ตํ์ญ์์ค.
์ฆ Promise์ ๋ฐํํ๋ฉด ๋จผ์ ๊ฒฐ๊ณผ๋ฅผ ํด๊ฒฐํด์ผํฉ๋๋ค.
class PQ<T> {
// ...
private setResult = (value: T | any, state: States) => {
const set = () => {
if (this.state !== States.PENDING) {
return null;
}
if (isThenable(value)) {
return (value as Thenable<T>).then(this.resolve, this.reject);
}
this.value = value;
this.state = state;
return this.executeHandlers();
};
setTimeout(set, 0);
};
}
๋จผ์ state๊ฐ pending
์ด ์๋์ง ํ์ธํฉ๋๋ค.
ํด๋น state์ธ ๊ฒฝ์ฐ Promise๊ฐ ์ด๋ฏธ ์ ํด์ง๊ณ ์๋ก์ด ๊ฐ์ ํ ๋น ํ ์ ์์ต๋๋ค.
๊ทธ๋ฐ ๋ค์ ๊ฐ์ด thenable
์ธ์ง ํ์ธํด์ผํฉ๋๋ค. ๊ฐ๋จํ ๋งํด thenable
์then
์ ๋ฉ์๋๋ก ์ฌ์ฉํ๋ ๊ฐ์ฒด์
๋๋ค.
์ผ๋ฐ์ ์ผ๋ก thenable
์ promise
์ฒ๋ผ ํ๋ํด์ผํฉ๋๋ค. ๊ฒฐ๊ณผ๋ฅผ ์ป๊ธฐ ์ํด ํธ์ถ ํ ๋ค์ this.resolve
๋ฐ this.reject
์ธ์๋ก ์ ๋ฌํฉ๋๋ค.
thenable
์ด ์ ํด์ง๋ฉด, ๋ฉ์๋ ์ค ํ๋๋ฅผ ํธ์ถํ๊ณ ์์ ๋ non-promise
๊ฐ์ ์ ๊ณต ํ ๊ฒ์
๋๋ค.
์ด์ ๊ฐ์ฒด๊ฐ thenable
์ธ์ง ํ์ธํด์ผํฉ๋๋ค.
describe('isThenable', () => {
test('detects objects with a then method', () => {
expect(isThenable({ then: () => null })).toBe(true);
expect(isThenable(null)).toBe(false);
expect(isThenable({})).toBe(false);
});
});
const isFunction = (func: any) => typeof func === 'function';
const isObject = (supposedObject: any) =>
typeof supposedObject === 'object' &&
supposedObject !== null &&
!Array.isArray(supposedObject);
const isThenable = (obj: any) => isObject(obj) && isFunction(obj.then);
callback
๋ด๋ถ์ ์ฝ๋๊ฐ ์๋๋ผ๋ promise๊ฐ ๋๊ธฐ๊ฐ๋์ง ์์ ๊ฒ์ด๋ผ๋ ์ ์ ์ธ์ํ๋ ๊ฒ์ด ์ค์ํฉ๋๋ค.
setTimeout
์ ์ฌ์ฉํ์ฌ ๋ค์ ์ด๋ฒคํธ ๋ฃจํ ๋ฐ๋ณต๊น์ง ์คํ์ ์ง์ฐ์ํต๋๋ค.
์ด์ ๋จ์ ๊ฒ์ ๊ฐ๊ณผ ์ํ๋ฅผ ์ค์ ํ ๋ค์ ๋ฑ๋ก ๋ ํธ๋ค๋ฌ๋ฅผ ์คํํ๋ ๊ฒ์
๋๋ค.
class PQ<T> {
// ...
private executeHandlers = () => {
if (this.state === States.PENDING) {
return null;
}
this.handlers.forEach((handler) => {
if (this.state === States.REJECTED) {
return handler.onFail(this.value);
}
return handler.onSuccess(this.value);
});
this.handlers = [];
};
}
๋ค์, ์ํ๊ฐ pending
์ด ์๋์ง ํ์ธํ์ญ์์ค.
promise์ ์ํ๋ ์ฐ๋ฆฌ๊ฐ ์ฌ์ฉํ ํจ์๋ฅผ ๋ํ๋
๋๋ค.
๋ฌธ์ ๊ฐ ํด๊ฒฐ๋๋ฉด onSuccess
๋ฅผ ์คํํ๊ณ ๊ทธ๋ ์ง ์์ผ๋ฉด onFail
์ ์คํํด์ผํฉ๋๋ค.
์ด์ ์์ ํ๊ณ ๋์ค์ ์ฐ์ฐํ ์คํ๋์ง ์๋๋ก ํธ๋ค๋ฌ ๋ฐฐ์ด์ ๋ถ๋ช ํ ํด๋ก์๋ค. ํธ๋ค๋ฌ๋ ๋์ค์๋ ๋ถ์ฐฉํ๊ณ ์คํ ํ ์ ์์ต๋๋ค.
๊ทธ ๋ค์์ ๋ค๋ฃจ์ด์ผ ํ ๋ด์ฉ์ ํธ๋ค๋ฌ๋ฅผ ์ฐ๊ฒฐํ๋ ๋ฐฉ๋ฒ์ ๋๋ค.
class PQ<T> {
// ...
private attachHandler = (handler: Handler<T, any>) => {
this.handlers = [...this.handlers, handler];
this.executeHandlers();
};
}
๋ณด์ด๋ ๊ฒ์ฒ๋ผ ๊ฐ๋จํฉ๋๋ค. ํธ๋ค๋ฌ ๋ฐฐ์ด์ ํธ๋ค๋ฌ๋ฅผ ์ถ๊ฐํ๊ณ ์คํํ๋ฉด๋ฉ๋๋ค. ์ด๊ฒ ๋ค์
๋๋ค.
์ด์ ๋ชจ๋ ๊ฒ์ ํฉ์น๋ ค๋ฉด then
๋ฉ์๋๋ฅผ ๊ตฌํํด์ผํฉ๋๋ค.
class PQ<T> {
// ...
public then<U>(
onSuccess?: HandlerOnSuccess<T, U>,
onFail?: HandlerOnFail<U>,
) {
return new PQ<U | T>((resolve, reject) => {
return this.attachHandler({
onSuccess: (result) => {
if (!onSuccess) {
return resolve(result);
}
try {
return resolve(onSuccess(result));
} catch (e) {
return reject(e);
}
},
onFail: (reason) => {
if (!onFail) {
return reject(reason);
}
try {
return resolve(onFail(reason));
} catch (e) {
return reject(e);
}
},
});
});
}
}
then
์์, ์ฐ๋ฆฌ๋ promise๋ฅผ ๋ฐํํ๊ณ ,callback
์์ ํ์ฌ promise๊ฐ ํ์ ๋๊ธฐ๋ฅผ ๊ธฐ๋ค๋ฆฌ๋๋ฐ ์ฌ์ฉ๋๋ ํธ๋ค๋ฌ๋ฅผ ์ฐ๊ฒฐํฉ๋๋ค.
์ด ๊ฒฝ์ฐ ํธ๋ค๋ฌ์ onSuccess
๋๋ onFail
์ด ์คํ๋๊ณ ๊ทธ์ ๋ฐ๋ผ ์งํ๋ฉ๋๋ค.
์ฌ๊ธฐ์ ๊ธฐ์ตํด์ผ ํ ๊ฒ์ ์ ๋ฌ๋ ํธ๋ค๋ฌ ์ค ์ด๋ ๊ฒ๋ ํ์ํ์ง ์๋ค๋ ๊ฒ์
๋๋ค. ๊ทธ๋ฌ๋ undefined
๋ฅผ ์คํํ๋ ค๊ณ ์๋ํ์ง ์๋ ๊ฒ์ด ์ค์ํฉ๋๋ค.
๋ํ ํธ๋ค๋ฌ๊ฐ ์ ๋ฌ ๋ ๋ onFail
์์ ์ค๋ฅ๊ฐ ์ฒ๋ฆฌ๋์์ผ๋ฏ๋ก ์ค์ ๋ก ๋ฆฌํด๋ promise๋ฅผ ํด๊ฒฐํฉ๋๋ค.
Catch
๋ ์ค์ ๋ก then
๋ฉ์๋์ ๋ํ ์ถ์ํ์
๋๋ค.
class PQ<T> {
// ...
public catch<U>(onFail: HandlerOnFail<U>) {
return this.then<U>(identity, onFail);
}
}
์ด๊ฒ ๋ค์ ๋๋ค.
Finally
๋ ๋ํ promise์ ๊ฒฐ๊ณผ์ ์ค์ ๋ก ์ ๊ฒฝ ์ฐ์ง ์๊ธฐ ๋๋ฌธ์ then(finallyCb, finallyCb)
์ ๋ํ ์ถ์ํ์
๋๋ค.
์ค์ ๋ก ์ด์ promise์ ๊ฒฐ๊ณผ๋ ๋ณด์กดํ๊ณ ๋ฐํํฉ๋๋ค. ๋ฐ๋ผ์ finallyCb
์ ์ํด ๋ฐํ๋๋ ๊ฒ์ ์ค์ ๋ก ์ค์ํ์ง ์์ต๋๋ค.
describe('PQ.prototype.finally', () => {
test('it is called regardless of the promise state', () => {
let counter = 0;
return PQ.resolve(15)
.finally(() => {
counter += 1;
})
.then(() => {
return PQ.reject(15);
})
.then(() => {
// wont be called
counter = 1000;
})
.finally(() => {
counter += 1;
})
.catch((reason) => {
expect(reason).toBe(15);
expect(counter).toBe(2);
});
});
});
class PQ<T> {
// ...
public finally<U>(cb: Finally<U>) {
return new PQ<U>((resolve, reject) => {
let val: U | any;
let isRejected: boolean;
return this.then(
(value) => {
isRejected = false;
val = value;
return cb();
},
(reason) => {
isRejected = true;
val = reason;
return cb();
},
).then(() => {
if (isRejected) {
return reject(val);
}
return resolve(val);
});
});
}
}
describe('PQ.prototype.toString', () => {
test('returns [object PQ]', () => {
expect(new PQ<undefined>((resolve) => resolve()).toString()).toBe(
'[object PQ]',
);
});
});
class PQ<T> {
// ...
public toString() {
return `[object PQ]`;
}
}
๋ฌธ์์ด [object PQ]
๋ง ๋ฐํํฉ๋๋ค.
promise์ ํต์ฌ์ ๊ตฌํ ํ ํ์๋ ์์ ์ธ๊ธ ํ Bluebird ๋ฉ์๋์ค ์ผ๋ถ๋ฅผ ๊ตฌํํ์ฌ promise๋ฅผ ๋ณด๋ค ์ฝ๊ฒ ์ํ ํ ์ ์์ต๋๋ค.
describe('PQ.resolve', () => {
test('resolves a value', () => {
return PQ.resolve(5).then((value) => {
expect(value).toBe(5);
});
});
});
class PQ<T> {
// ...
public static resolve<U = any>(value?: U | Thenable<U>) {
return new PQ<U>((resolve) => {
return resolve(value);
});
}
}
describe('PQ.reject', () => {
test('rejects a value', () => {
return PQ.reject(5).catch((value) => {
expect(value).toBe(5);
});
});
});
class PQ<T> {
// ...
public static reject<U>(reason?: any) {
return new PQ<U>((resolve, reject) => {
return reject(reason);
});
}
}
describe('PQ.all', () => {
test('resolves a collection of promises', () => {
return PQ.all([PQ.resolve(1), PQ.resolve(2), 3]).then((collection) => {
expect(collection).toEqual([1, 2, 3]);
});
});
test('rejects if one item rejects', () => {
return PQ.all([PQ.resolve(1), PQ.reject(2)]).catch((reason) => {
expect(reason).toBe(2);
});
});
});
class PQ<T> {
// ...
public static all<U = any>(collection: (U | Thenable<U>)[]) {
return new PQ<U[]>((resolve, reject) => {
if (!Array.isArray(collection)) {
return reject(new TypeError('An array must be provided.'));
}
let counter = collection.length;
const resolvedCollection: U[] = [];
const tryResolve = (value: U, index: number) => {
counter -= 1;
resolvedCollection[index] = value;
if (counter !== 0) {
return null;
}
return resolve(resolvedCollection);
};
return collection.forEach((item, index) => {
return PQ.resolve(item)
.then((value) => {
return tryResolve(value, index);
})
.catch(reject);
});
});
}
}
๊ตฌํ์ด ๋งค์ฐ ๊ฐ๋จํ๋ค๊ณ ์๊ฐํฉ๋๋ค.
collection.length
์์ ์์ํ์ฌ 0์ ๋๋ฌ ํ ๋๊น์ง ๊ฐ tryResolve'
๋ก ์นด์ดํธ ๋ค์ดํฉ๋๋ค. ์ด๋ ์ปฌ๋ ์
์ ๋ชจ๋ ํญ๋ชฉ์ด ํด๊ฒฐ๋์์์ ์๋ฏธํฉ๋๋ค. ๊ทธ๋ฐ ๋ค์ ์๋ก ๋ง๋ ์ปฌ๋ ์
์ ํด๊ฒฐํฉ๋๋ค.
describe('PQ.any', () => {
test('resolves the first value', () => {
return PQ.any<number>([
PQ.resolve(1),
new PQ((resolve) => setTimeout(resolve, 15)),
]).then((val) => expect(val).toBe(1));
});
test('rejects if the first value rejects', () => {
return PQ.any([
new PQ((resolve) => setTimeout(resolve, 15)),
PQ.reject(1),
]).catch((reason) => {
expect(reason).toBe(1);
});
});
});
class PQ<T> {
// ...
public static any<U = any>(collection: (U | Thenable<U>)[]) {
return new PQ<U>((resolve, reject) => {
return collection.forEach((item) => {
return PQ.resolve(item)
.then(resolve)
.catch(reject);
});
});
}
}
์ฐ๋ฆฌ๋ ๋จ์ํ ์ฒซ ๋ฒ์งธ ๊ฐ์ด ํด๊ฒฐ๋์ด Promise๋ก ๋์ ์ค๊ธฐ๋ฅผ ๊ธฐ๋ค๋ฆฝ๋๋ค.
describe('PQ.props', () => {
test('resolves object correctly', () => {
return PQ.props<{ test: number; test2: number }>({
test: PQ.resolve(1),
test2: PQ.resolve(2),
}).then((obj) => {
return expect(obj).toEqual({ test: 1, test2: 2 });
});
});
test('rejects non objects', () => {
return PQ.props([]).catch((reason) => {
expect(reason).toBeInstanceOf(TypeError);
});
});
});
class PQ<T> {
// ...
public static props<U = any>(obj: object) {
return new PQ<U>((resolve, reject) => {
if (!isObject(obj)) {
return reject(new TypeError('An object must be provided.'));
}
const resolvedObject = {};
const keys = Object.keys(obj);
const resolvedValues = PQ.all<string>(keys.map((key) => obj[key]));
return resolvedValues
.then((collection) => {
return collection.map((value, index) => {
resolvedObject[keys[index]] = value;
});
})
.then(() => resolve(resolvedObject as U))
.catch(reject);
});
}
}
์ ๋ฌ ๋ ๊ฐ์ฒด์ ํค๋ฅผ ๋ฐ๋ณตํ์ฌ ๋ชจ๋ ๊ฐ์ ํด๊ฒฐํฉ๋๋ค. ๊ทธ๋ฐ ๋ค์ ์ ๊ฐ์ฒด์ ๊ฐ์ ํ ๋นํ๊ณ Promise๋ฅผ ํด๊ฒฐํฉ๋๋ค.
describe('PQ.protoype.spread', () => {
test('spreads arguments', () => {
return PQ.all<number>([1, 2, 3]).spread((...args) => {
expect(args).toEqual([1, 2, 3]);
return 5;
});
});
test('accepts normal value (non collection)', () => {
return PQ.resolve(1).spread((one) => {
expect(one).toBe(1);
});
});
});
class PQ<T> {
// ...
public spread<U>(handler: (...args: any[]) => U) {
return this.then<U>((collection) => {
if (Array.isArray(collection)) {
return handler(...collection);
}
return handler(collection);
});
}
}
describe('PQ.delay', () => {
test('waits for the given amount of miliseconds before resolving', () => {
return new PQ<string>((resolve) => {
setTimeout(() => {
resolve('timeout');
}, 50);
return PQ.delay(40).then(() => resolve('delay'));
}).then((val) => {
expect(val).toBe('delay');
});
});
test('waits for the given amount of miliseconds before resolving 2', () => {
return new PQ<string>((resolve) => {
setTimeout(() => {
resolve('timeout');
}, 50);
return PQ.delay(60).then(() => resolve('delay'));
}).then((val) => {
expect(val).toBe('timeout');
});
});
});
class PQ<T> {
// ...
public static delay(timeInMs: number) {
return new PQ((resolve) => {
return setTimeout(resolve, timeInMs);
});
}
}
setTimeout
์ ์ฌ์ฉํจ์ผ๋ก์จ, ์ฃผ์ด์ง ๋ฐ๋ฆฌ ์ด๋งํผ resolve
ํจ์์ ์คํ์ ์ง์ฐ์ํต๋๋ค.
describe('PQ.prototype.timeout', () => {
test('rejects after given timeout', () => {
return new PQ<number>((resolve) => {
setTimeout(resolve, 50);
})
.timeout(40)
.catch((reason) => {
expect(reason).toBeInstanceOf(PQ.errors.TimeoutError);
});
});
test('resolves before given timeout', () => {
return new PQ<number>((resolve) => {
setTimeout(() => resolve(500), 500);
})
.timeout(600)
.then((value) => {
expect(value).toBe(500);
});
});
});
class PQ<T> {
// ...
public timeout(timeInMs: number) {
return new PQ<T>((resolve, reject) => {
const timeoutCb = () => {
return reject(new PQ.errors.TimeoutError());
};
setTimeout(timeoutCb, timeInMs);
return this.then(resolve);
});
}
}
์ด๊ฒ์ ์กฐ๊ธ ๊น๋ค ๋กญ์ต๋๋ค.
setTimeout
์ด promise
์์๋ณด๋ค ๋ ๋น ๋ฅด๊ฒ ์คํ๋๋ฉด ํน๋ณํ ์ค๋ฅ๋ก promise
๋ฅผ ๊ฑฐ์ ํฉ๋๋ค.
describe('PQ.promisify', () => {
test('works', () => {
const getName = (firstName, lastName, callback) => {
return callback(null, `${firstName} ${lastName}`);
};
const fn = PQ.promisify<string>(getName);
const firstName = 'Maciej';
const lastName = 'Cieslar';
return fn(firstName, lastName).then((value) => {
return expect(value).toBe(`${firstName} ${lastName}`);
});
});
});
class PQ<T> {
// ...
public static promisify<U = any>(
fn: (...args: any[]) => void,
context = null,
) {
return (...args: any[]) => {
return new PQ<U>((resolve, reject) => {
return fn.apply(context, [
...args,
(err: any, result: U) => {
if (err) {
return reject(err);
}
return resolve(result);
},
]);
});
};
}
}
์ ๋ฌ ๋ ๋ชจ๋ ์ธ์์ ๋ง์ง๋ง ์ธ์๋ก ํจ์์ ์ ์ฉํ์ฌ ์ค๋ฅ ์ฐ์ callback
์ ์ ๊ณตํฉ๋๋ค.
describe('PQ.promisifyAll', () => {
test('promisifies a object', () => {
const person = {
name: 'Maciej Cieslar',
getName(callback) {
return callback(null, this.name);
},
};
const promisifiedPerson = PQ.promisifyAll<{
getNameAsync: () => PQ<string>;
}>(person);
return promisifiedPerson.getNameAsync().then((name) => {
expect(name).toBe('Maciej Cieslar');
});
});
});
class PQ<T> {
// ...
public static promisifyAll<U>(obj: any): U {
return Object.keys(obj).reduce((result, key) => {
let prop = obj[key];
if (isFunction(prop)) {
prop = PQ.promisify(prop, obj);
}
result[`${key}Async`] = prop;
return result;
}, {}) as U;
}
}
์ฐ๋ฆฌ๋ ๊ฐ์ฒด์ promisify
์ ํค๋ฅผ ๋ฐ๋ณตํ์๊ณ ์ด๊ฒ์ ๊ฐ๊ฐ Async
๋ผ๋ ๋ฉ์๋์ ์ด๋ฆ์ผ๋ก ์ถ๊ฐํฉ๋๋ค.
์ฌ๊ธฐ์๋ ๋ชจ๋ Bluebird API ๋ฉ์๋ ์ค ๋ช ๊ฐ์ง๋ง ํฌํจ๋์ด ์์ผ๋ฏ๋ก ๋๋จธ์ง๋ฅผ ํ์ํ๊ณ ํด๊ฒฐํ๊ณ ๊ตฌํํด ๋ณด์๊ธฐ ๋ฐ๋๋๋ค.
์ฒ์์๋ ์ด๋ ค์ ๋ณด์ผ ์ ์์ง๋ง ๋๋ดํ์ง๋ ๋ง์ญ์์ค. ์ฌ์ฐ๋ฉด ๊ฐ์น๊ฐ ์์ต๋๋ค.
์ฝ์ด ์ฃผ์ ์ ๊ฐ์ฌํฉ๋๋ค! ์ด ๊ธฐ์ฌ๊ฐ ์ ์ตํ๊ณ Promise์ ๊ฐ๋ ์ ์ดํดํ๋ ๋ฐ ๋์์ด ๋์๊ธฐ๋ฅผ ๋ฐ๋๋๋ค. ์์ผ๋ก๋ Promise์ ์ฌ์ฉํ๊ฑฐ๋ ๋จ์ํ ๋น๋๊ธฐ ์ฝ๋๋ฅผ ์์ฑํ๋ ๊ฒ์ด ๋ ํธ์ํด์ง๊ฒ์ ๋๋ค.
์ง๋ฌธ์ด๋ ์๊ฒฌ์ด ์์ผ์๋ฉด ์๋์ ์๊ฒฌ ์น์ ์ ์์ ๋กญ๊ฒ ์๊ฒฌ์ ๋ณด๋ด๊ฑฐ๋ ๋ฉ์์ง๋ฅผ ๋ณด๋ด์ฃผ์ญ์์ค.
๋ด ์์ ๋ฏธ๋์ด๋ฅผ ํ์ธํ์ญ์์ค!
๋ด ๋ด์ค ๋ ํฐ์ ๊ฐ์ ํ์ญ์์ค!
Originally published at www.mcieslar.com on August 4, 2018.