손 으로 Promise 를 실현 하 는 방법 을 알려 드릴 게 요.
많은 자 바스 크 립 트 의 초보 자 들 은 프로 미스 문법 을 익 혀 야 해탈 할 수 있다 는 지옥 지배 로 되 돌아 가 는 두려움 을 느 낀 적 이 있다.많은 언어 들 이 Promise 를 내 장 했 지만 자 바스 크 립 트 에서 이 를 진정 으로 빛 낸 것 은 jQuery 1.5 쌍
$.ajax
의 재 구성 으로 Promise 를 지 지 했 고 용법 도 jQuery 가 추앙 하 는 체인 호출 과 맞 아 떨 어 졌 다.그 후에 ES6 가 태 어 나 서 야 모두 가 전 국민 Promise 의 시대 에 들 어 섰 고 그 후에 ES8 은 async 문법 을 도입 하여 자 바스 크 립 트 의 비동기 적 인 문법 을 더욱 우아 하 게 만 들 었 다.오늘 우 리 는 한 걸음 한 걸음 Promise 를 실현 할 것 입 니 다.만약 당신 이 Promise 를 사용 한 적 이 없다 면 먼저 Promise 문법 을 익히 고 본문 을 읽 는 것 을 권장 합 니 다.
구조 함수
기 존Promise/A+규범 에 서 는 promise 대상 이 어디에서 왔 는 지 규정 하지 않 았 으 며,jQuery 에 서 는 호출
$.Deferred()
을 통 해 promise 대상 을 얻 었 고,ES6 에 서 는 Promise 류 를 예화 하여 promise 대상 을 얻 었 다.여기 서 우 리 는 ES 의 문법 을 사용 하여 하나의 유형 을 구성 하고 실례 화 된 방식 으로 promise 대상 을 되 돌려 줍 니 다.Promise 가 이미 존재 하기 때문에 우 리 는 잠시 이 유형의 이름 을Deferred
로 지 었 습 니 다.
class Deferred {
constructor(callback) {
const resolve = () => {
// TODO
}
const reject = () => {
// TODO
}
try {
callback(resolve, reject)
} catch (error) {
reject(error)
}
}
}
구조 함수 가 콜백 을 받 아들 이 고 콜백 을 호출 할 때 resolve,reject 두 가지 방법 을 입력 해 야 합 니 다.Promise 의 상태
Promise 는 모두 세 가지 상태 로 나 뉜 다.
pending
:대기 중 입 니 다.이것 은 Promise 의 초기 상태 입 니 다.fulfilled
:끝 났 습 니 다.resolve 상 태 를 정상적으로 호출 합 니 다.rejected
:거부 되 었 습 니 다.내부 에 오류 가 발생 하거나 reject 를 호출 한 후의 상태 입 니 다.Promise 가 실행 되 는 동안
[[PromiseState]]
에 저 장 된 상 태 를 볼 수 있 습 니 다.다음은 Deferred 에 상 태 를 추가 합 니 다.
//
const STATUS = {
PENDING: 'PENDING',
FULFILLED: 'FULFILLED',
REJECTED: 'REJECTED'
}
class Deferred {
constructor(callback) {
this.status = STATUS.PENDING
const resolve = () => {
// TODO
}
const reject = () => {
// TODO
}
try {
callback(resolve, reject)
} catch (error) {
// reject
reject(error)
}
}
}
초기 브 라 우 저의 구현 중 fulfilled 상 태 는 resolved 로 Promise 규범 과 맞지 않 습 니 다.물론 지금 은 복구 되 었 습 니 다.내부 결과
상 태 를 제외 하고 Promise 내부 에 resolve/reject 가 받 아들 인 값 을 임시 저장 하 는 결과
[[PromiseResult]]
가 있 습 니 다.구조 함수 에 내부 결 과 를 계속 추가 합 니 다.
class Deferred {
constructor(callback) {
this.value = undefined
this.status = STATUS.PENDING
const resolve = value => {
this.value = value
// TODO
}
const reject = reason => {
this.value = reason
// TODO
}
try {
callback(resolve, reject)
} catch (error) {
// reject
reject(error)
}
}
}
저장 반전Promise 를 사용 할 때 우 리 는 보통 promise 대상
.then
방법 을 호출 하여 promise 상태 가fulfilled
또는rejected
로 바 뀌 었 을 때 내부 결 과 를 얻 은 다음 에 후속 처 리 를 한다.그래서 구조 함수 에서 두 개의 배열 을 구성 하여.then
방법 으로 들 어 오 는 반전 을 저장 해 야 한다.
class Deferred {
constructor(callback) {
this.value = undefined
this.status = STATUS.PENDING
this.rejectQueue = []
this.resolveQueue = []
const resolve = value => {
this.value = value
// TODO
}
const reject = reason => {
this.value = reason
// TODO
}
try {
callback(resolve, reject)
} catch (error) {
// reject
reject(error)
}
}
}
resolve 와 reject상태 수정
그 다음 에 우 리 는 resolve 와 reject 두 가지 방법 을 실현 해 야 한다.이 두 가지 방법 은 호출 될 때 promise 대상 의 상 태 를 바 꿀 것 이다.그리고 어떤 방법 이 호출 된 후에 다른 방법 은 호출 될 수 없다.
new Promise((resolve, reject) => {
setTimeout(() => {
resolve('🙆♂️')
}, 500)
setTimeout(() => {
reject('🙅♂️')
}, 800)
}).then(
() => {
console.log('fulfilled')
},
() => {
console.log('rejected')
}
)
이때 콘 솔 은 출력
fulfilled
만 할 뿐 나타 나 지 않 는 다rejected
.
class Deferred {
constructor(callback) {
this.value = undefined
this.status = STATUS.PENDING
this.rejectQueue = []
this.resolveQueue = []
let called //
const resolve = value => {
if (called) return
called = true
this.value = value
//
this.status = STATUS.FULFILLED
}
const reject = reason => {
if (called) return
called = true
this.value = reason
//
this.status = STATUS.REJECTED
}
try {
callback(resolve, reject)
} catch (error) {
// reject
reject(error)
}
}
}
호출 반전상 태 를 수정 한 후 결 과 를 얻 은 promise 는 보통 then 방법 으로 들 어 오 는 반전 을 호출 합 니 다.
class Deferred {
constructor(callback) {
this.value = undefined
this.status = STATUS.PENDING
this.rejectQueue = []
this.resolveQueue = []
let called //
const resolve = value => {
if (called) return
called = true
this.value = value
//
this.status = STATUS.FULFILLED
//
for (const fn of this.resolveQueue) {
fn(this.value)
}
}
const reject = reason => {
if (called) return
called = true
this.value = reason
//
this.status = STATUS.REJECTED
//
for (const fn of this.rejectQueue) {
fn(this.value)
}
}
try {
callback(resolve, reject)
} catch (error) {
// reject
reject(error)
}
}
}
자 바스 크 립 트 이벤트 시스템 을 잘 아 는 학생 들 은promise.then
방법 중의 리 셋 이 마이크로 퀘 스 트 대기 열 에 놓 인 다음 에 다른 단계 로 호출 된다 는 것 을 알 아야 한다.따라서,우 리 는 리 셋 된 호출 을 비동기 대기 열 에 넣 어야 합 니 다.여기 서 우 리 는 setTimeout 에 넣 어 지연 호출 을 할 수 있 습 니 다.비록 규범 에 부합 되 지 는 않 지만,아 쉬 운 대로 할 수 있 습 니 다.
class Deferred {
constructor(callback) {
this.value = undefined
this.status = STATUS.PENDING
this.rejectQueue = []
this.resolveQueue = []
let called //
const resolve = value => {
if (called) return
called = true
//
setTimeout(() => {
this.value = value
//
this.status = STATUS.FULFILLED
//
for (const fn of this.resolveQueue) {
fn(this.value)
}
})
}
const reject = reason => {
if (called) return
called = true
//
setTimeout(() =>{
this.value = reason
//
this.status = STATUS.REJECTED
//
for (const fn of this.rejectQueue) {
fn(this.value)
}
})
}
try {
callback(resolve, reject)
} catch (error) {
// reject
reject(error)
}
}
}
then 방법그 다음 에 우 리 는 then 방법 을 실현 해 야 한다.Promise 를 사용 한 학생 들 은 then 방법 이 체인 호출 을 계속 할 수 있다 는 것 을 알 고 있 을 것 이다.그래서 then 은 반드시 promise 대상 으로 돌아 가 야 한다.그러나
Promise/A+
규범 에 명확 한 규정 이 있 습 니 다.then 방법 은 this 로 직접 돌아 가 는 것 이 아니 라 새로운 promise 대상 으로 돌아 가 는 것 입 니 다.이 점 은 다음 코드 를 통 해 검증 할 수 있 습 니 다.p1
대상 과p2
는 서로 다른 대상 이 고 then 방법 으로 되 돌아 오 는p2
대상 도 Promise 의 인 스 턴 스 임 을 알 수 있다.그 밖 에 then 방법 은 현재 상 태 를 판단 해 야 합 니 다.현재 상태 가
pending
상태 가 아니라면 들 어 오 는 리 셋 을 직접 호출 할 수 있 습 니 다.대기 열 에 넣 지 않 고 기다 릴 수 있 습 니 다.
class Deferred {
then(onResolve, onReject) {
if (this.status === STATUS.PENDING) {
//
const rejectQueue = this.rejectQueue
const resolveQueue = this.resolveQueue
return new Deferred((resolve, reject) => {
//
resolveQueue.push(function (innerValue) {
try {
const value = onResolve(innerValue)
// promise
resolve(value)
} catch (error) {
reject(error)
}
})
//
rejectQueue.push(function (innerValue) {
try {
const value = onReject(innerValue)
// promise
resolve(value)
} catch (error) {
reject(error)
}
})
})
} else {
const innerValue = this.value
const isFulfilled = this.status === STATUS.FULFILLED
return new Deferred((resolve, reject) => {
try {
const value = isFulfilled
? onResolve(innerValue) // onResolve
: onReject(innerValue) // onReject
resolve(value) // then
} catch (error) {
reject(error)
}
})
}
}
}
현재 우리 의 논 리 는 이미 기본적으로 통 할 수 있 습 니 다.우 리 는 먼저 코드 를 실행 해 보 겠 습 니 다.
new Deferred(resolve => {
setTimeout(() => {
resolve(1)
}, 3000)
}).then(val1 => {
console.log('val1', val1)
return val1 * 2
}).then(val2 => {
console.log('val2', val2)
return val2
})
3 초 후 콘 솔 에 다음 과 같은 결과 가 나 타 났 습 니 다.이것 은 기본적으로 우리 의 기대 에 부합 되 는 것 을 볼 수 있다.
값 관통
만약 에 우리 가 then 을 호출 할 때 어떠한 인자 도 들 어 오지 않 으 면 규범 에 따라 현재 promise 의 값 은 다음 then 방법 으로 전달 할 수 있 습 니 다.예 를 들 어 다음 코드:
new Deferred(resolve => {
resolve(1)
})
.then()
.then()
.then(val => {
console.log(val)
})
콘 솔 에 서 는 출력 을 보지 못 하고 Promise 로 전환 하면 정확 한 결 과 를 볼 수 있 습 니 다.
이 방법 을 해결 하려 면 then 호출 할 때 매개 변수 가 함수 인지 아 닌 지 를 판단 하고 그렇지 않 으 면 기본 값 을 줘 야 합 니 다.
const isFunction = fn => typeof fn === 'function'
class Deferred {
then(onResolve, onReject) {
//
onReject = isFunction(onReject) ? onReject : reason => { throw reason }
onResolve = isFunction(onResolve) ? onResolve : value => { return value }
if (this.status === STATUS.PENDING) {
// ...
} else {
// ...
}
}
}
이제 우 리 는 이미 정확 한 결 과 를 얻 을 수 있다.
가 까 운 거리.
지금 우 리 는 완벽 하 게 then 방법 을 실현 하 는 데 한 걸음 밖 에 남지 않 았 다.그것 은 바로 우리 가 then 방법 으로 들 어 온
onResolve/onReject
리 턴 을 호출 할 때 그들의 반환 치 를 판단 해 야 한 다 는 것 이다.만약 반전 의 내부 가 promise 대상 으로 돌아간다 면 우 리 는 어떻게 처리 해 야 합 니까?혹은 순환 인용 이 나타 나 면 우 리 는 어떻게 처리 해 야 합 니까?앞에서 우 리 는
onResolve/onReject
의 반환 치 를 받 은 후에 바로resolve
또는resolve
를 호출 했다.지금 우 리 는 그들의 반환 치 를 처리 해 야 한다.
then(onResolve, onReject) {
//
if (this.status === STATUS.PENDING) {
//
const rejectQueue = this.rejectQueue
const resolveQueue = this.resolveQueue
const promise = new Deferred((resolve, reject) => {
//
resolveQueue.push(function (innerValue) {
try {
const value = onResolve(innerValue)
- resolve(value)
+ doThenFunc(promise, value, resolve, reject)
} catch (error) {
reject(error)
}
})
//
rejectQueue.push(function (innerValue) {
try {
const value = onReject(innerValue)
- resolve(value)
+ doThenFunc(promise, value, resolve, reject)
} catch (error) {
reject(error)
}
})
})
return promise
} else {
const innerValue = this.value
const isFulfilled = this.status === STATUS.FULFILLED
const promise = new Deferred((resolve, reject) => {
try {
const value = isFulfilled
? onResolve(innerValue) // onResolve
: onReject(innerValue) // onReject
- resolve(value)
+ doThenFunc(promise, value, resolve, reject)
} catch (error) {
reject(error)
}
})
return promise
}
}
반환 값 판단우리 가 Promise 를 사용 할 때 는 then 방법 에서 새로운 Promise 를 되 돌려 주 고 새로운 Promise 가 완 성 된 내부 결 과 를 뒤의 then 방법 에 전달 하 는 경우 가 많다.
fetch('server/login')
.then(user => {
// promise
return fetch(`server/order/${user.id}`)
})
.then(order => {
console.log(order)
})
function doThenFunc(promise, value, resolve, reject) {
// value promise
if (value instanceof Deferred) {
// then ,
value.then(
function (val) {
doThenFunc(promise, value, resolve, reject)
},
function (reason) {
reject(reason)
}
)
return
}
// promise ,
resolve(value)
}
판단 순환 참조현재 then 방법 리 셋 함수 리 턴 값 이 현재 then 방법 으로 생 성 된 새로운 promise 대상 이 라면 순환 참조 로 여 겨 집 니 다.구체 적 인 사례 는 다음 과 같 습 니 다.
then 방법 으로 되 돌아 오 는 새로운 promise 대상
p1
은 리 턴 에서 반환 값 으로 간주 되 며,이 때 이상 을 던 집 니 다.이전의 논리 에 따 르 면 코드 는 이 논리 에 계속 갇 혀 있 을 것 이기 때문이다.그래서 우 리 는 사전에 예방 하고 제때에 잘못 을 던 져 야 한다.
function doThenFunc(promise, value, resolve, reject) {
//
if (promise === value) {
reject(
new TypeError('Chaining cycle detected for promise')
)
return
}
// value promise
if (value instanceof Deferred) {
// then ,
value.then(
function (val) {
doThenFunc(promise, value, resolve, reject)
},
function (reason) {
reject(reason)
}
)
return
}
// promise ,
resolve(value)
}
이제 then 에서 새로운 promise 대상 으로 돌아 가 보 자.
const delayDouble = (num, time) => new Deferred((resolve) => {
console.log(new Date())
setTimeout(() => {
resolve(2 * num)
}, time)
})
new Deferred(resolve => {
setTimeout(() => {
resolve(1)
}, 2000)
})
.then(val => {
console.log(new Date(), val)
return delayDouble(val, 2000)
})
.then(val => {
console.log(new Date(), val)
})
위의 결과 도 우리 의 기대 에 완벽 하 게 부합 되 었 다.
catch 방법
catch 방법 은 사실 매우 간단 하 다.then 방법의 약자 에 해당 한다.
class Deferred {
constructor(callback) {}
then(onResolve, onReject) {}
catch(onReject) {
return this.then(null, onReject)
}
}
정적 방법resolve/reject
Promise 클래스 는 두 가지 정적 방법 을 제공 하여 상태 가 고정된 promise 대상 을 직접 되 돌려 줍 니 다.
class Deferred {
constructor(callback) {}
then(onResolve, onReject) {}
catch(onReject) {}
static resolve(value) {
return new Deferred((resolve, reject) => {
resolve(value)
})
}
static reject(reason) {
return new Deferred((resolve, reject) => {
reject(reason)
})
}
}
allall 방법 은 하나의 promise 대상 의 배열 을 받 아들 이 고 배열 의 모든 promise 대상 의 상태 가
fulfilled
로 바 뀌 면 결 과 를 되 돌려 줍 니 다.그 결과 도 하나의 배열 이 고 배열 의 모든 값 은 promise 대상 의 내부 결과 에 대응 합 니 다.우선,우 리 는 먼저 들 어 오 는 매개 변수 가 배열 인지 아 닌 지 를 판단 한 다음 에 결과 배열 과 새로운 promise 대상 을 구성 해 야 한다.
class Deferred {
static all(promises) {
// ,
if (!Array.isArray(promises)) {
return Deferred.reject(new TypeError('args must be an array'))
}
// promise
const result = []
const length = promises.length
// remaining , promise fulfilled
let remaining = length
const promise = new Deferred(function (resolve, reject) {
// TODO
})
return promise
}
}
그 다음 에 우 리 는 모든 promise 대상 의 resolve 를 차단 하고 매번 resolveremaining
를 1 로 줄 여remaining
를 0 으로 돌려 야 한 다 는 판단 을 해 야 한다.
class Deferred {
static all(promises) {
// ,
if (!Array.isArray(promises)) {
return Deferred.reject(new TypeError('args must be an array'))
}
const result = [] // promise
const length = promises.length
let remaining = length
const promise = new Deferred(function (resolve, reject) {
// ,
if (promises.length === 0) return resolve(result)
function done(index, value) {
doThenFunc(
promise,
value,
(val) => {
// resolve result
result[index] = val
if (--remaining === 0) {
// promise
//
resolve(result)
}
},
reject
)
}
//
setTimeout(() => {
for (let i = 0; i < length; i++) {
done(i, promises[i])
}
})
})
return promise
}
}
다음은 다음 코드 를 통 해 논리 가 정확 한 지 판단 합 니 다.예상 한 대로 코드 가 실 행 된 후 3 초 후에 콘 솔 은 배열[2, 4, 6]
을 인쇄 합 니 다.
const delayDouble = (num, time) => new Deferred((resolve) => {
setTimeout(() => {
resolve(2 * num)
}, time)
})
console.log(new Date())
Deferred.all([
delayDouble(1, 1000),
delayDouble(2, 2000),
delayDouble(3, 3000)
]).then((results) => {
console.log(new Date(), results)
})
위의 운행 결 과 는 기본적으로 우리 의 기대 에 부합된다.
race
race 방법 역시 promise 대상 의 배열 을 받 아들 이지 만 하나의 promise 가
fulfilled
상태 로 바 뀌 면 결 과 를 되 돌려 줍 니 다.
class Deferred {
static race(promises) {
if (!Array.isArray(promises)) {
return Deferred.reject(new TypeError('args must be an array'))
}
const length = promises.length
const promise = new Deferred(function (resolve, reject) {
if (promises.length === 0) return resolve([])
function done(value) {
doThenFunc(promise, value, resolve, reject)
}
//
setTimeout(() => {
for (let i = 0; i < length; i++) {
done(promises[i])
}
})
})
return promise
}
}
다음은 앞에서 all 방법 을 검증 한 사례 를 race 로 바 꿉 니 다.예상 한 대로 코드 가 실 행 된 후 1 초 후에 콘 솔 에서 2 를 인쇄 합 니 다.
const delayDouble = (num, time) => new Deferred((resolve) => {
setTimeout(() => {
resolve(2 * num)
}, time)
})
console.log(new Date())
Deferred.race([
delayDouble(1, 1000),
delayDouble(2, 2000),
delayDouble(3, 3000)
]).then((results) => {
console.log(new Date(), results)
})
위의 운행 결 과 는 기본적으로 우리 의 기대 에 부합된다.
총결산
간단 한 Promise 류 가 이미 실현 되 었 습 니 다.여 기 는 일부 세부 사항 을 생략 하고 전체 코드 는 github 에 접근 할 수 있 습 니 다.Promise 의 등장 은 후기의 async 문법 에 튼튼한 기반 을 다 져 주 었 다.다음 블 로 그 는 JavaScript 의 비동기 프로 그래 밍 사 에 대해 잘 이야기 할 수 있 고 실수 로 또 자신 에 게 구 덩이 를 파 주 었 다.
Promise 를 실현 하 는 방법 을 손 으로 가르쳐 주 는 이 글 은 여기까지 소개 되 었 습 니 다.더 많은 Promise 문법 내용 은 우리 의 이전 글 을 검색 하거나 아래 의 관련 글 을 계속 읽 어 보 세 요.앞으로 도 많은 응원 부 탁 드 리 겠 습 니 다!
이 내용에 흥미가 있습니까?
현재 기사가 여러분의 문제를 해결하지 못하는 경우 AI 엔진은 머신러닝 분석(스마트 모델이 방금 만들어져 부정확한 경우가 있을 수 있음)을 통해 가장 유사한 기사를 추천합니다:
Promise 단순화텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
CC BY-SA 2.5, CC BY-SA 3.0 및 CC BY-SA 4.0에 따라 라이센스가 부여됩니다.