IT박스

기본 Javascript 약속 구현 시도

itboxs 2020. 12. 24. 23:27
반응형

기본 Javascript 약속 구현 시도


프라 미스가 자바 스크립트에서 어떻게 작동하는지 더 잘 이해하기 위해 필자는 기본 구현을 직접 시도하고 코딩하기로 결정했습니다.

기본적으로 함수를 인수로 사용하는 Promises Object (코드에서 Aaa라고 부름)를 구현하고 싶습니다. 이 함수는 resolvepromise 대한 resolve를 호출 하거나 거부 할 수 reject있습니다. 기본 구현 및 사용법은 다음과 같습니다. 두 번째 인수가 약속 사양에 따라 허용되는지 확실하지 않지만 지금까지 얻은 것입니다.

Aaa=function(f,pause) { 

    console.log("ggg");

    var t=this;
    this.f=f;
    this.thens=[];

    this.resolve=function(g) {

        for(var i=0;i<t.thens.length;i++)
        {
            // try/catch to be used later for dealing with exceptions

            try
            {
                t.thens[i].f(g);
                t.thens[i].resolve();
            }   
            catch(ex)
            {}

        }
    };  

    // to be implemented later
    this.reject=function(g) {};

    this.then=function(resolve,reject) {

        // i'm passing true for pause argument as we dont need to execute promise code just yet
        var nextPromise=new Aaa(resolve,true);

        this.thens.push(nextPromise);

        return nextPromise;
    }


    if(!pause)
        this.f(this.resolve,this.reject); 

}


var aaa=new Aaa(function(resolve,reject) {

    console.log("aaa");

    setTimeout(function() {

        console.log("fff");
        resolve("good");

    },2000);

    console.log("bbb");

});

이제 약속을 만들고 호출하고 해결할 수 있습니다. then메서드는 새로운 Aaa (Promise)를 반환하므로이를 연결할 수 있습니다. 이제 아래 코드는 위에서 만든 promise를 사용하고 then콜백을 연결합니다. 각각 then은 새로운 약속을 반환하며이 경우 제대로 작동하는 것 같습니다.

aaa.then(function(res) {

    console.log("ccc");
    console.log(res);

})
.then(function(res) {
    console.log("ddd");
    console.log(res);
},function(rej) {
    console.log("eee");
    console.log(rej);
});

내가 얻는 출력은 다음과 같습니다.

ggg
aaa 
bbb 
ggg 
ggg 
fff 
ccc 
good 
ddd 
undefined 

그러나 문제는 then호출 중 하나가 약속을 반환 할 때입니다 .

aaa.then(function(res) {

    console.log("ccc");
    console.log(res);

    // here we return the promise manually. then next then call where "ddd" is output should not be called UNTIL this promise is resolved. How to do that?

        return new Aaa(function(resolve,reject) {

        console.log("iii");

        setTimeout(function() {
        console.log("kkk");
            resolve("good2");
            // reject("bad");

        },2000);

        console.log("jjj");

    }).then(function (res) {
        console.log("lll");

        console.log(res);
    });

})
.then(function(res) {
    console.log("ddd");
    console.log(res);
},function(rej) {
    console.log("eee");
    console.log(rej);
});

출력은 다음과 같습니다.

ggg 
aaa 
bbb 
ggg 
ggg  
fff  
ccc  
good  
ggg  
iii  
jjj  
ggg  
ddd  
undefined  
kkk  
lll  
good2 

그런 다음 where dddis output 호출 은 방금 추가 한 반환 된 promise가 해결 될 때까지 호출되지 않아야합니다.

어떻게 구현하는 것이 가장 좋을까요?


여기서 처리하지 않는 케이스가 많이 있습니다. 가장 좋은 방법은 상태 머신으로서 프라 미스를 구축하는 것부터 시작하는 것입니다.

var PENDING = 0;
var FULFILLED = 1;
var REJECTED = 2;

function Promise() {

  // store state which can be PENDING, FULFILLED or REJECTED
  var state = PENDING;

  // store value once FULFILLED or REJECTED
  var value = null;

  // store sucess & failure handlers
  var handlers = [];
}

이제 나머지 구현을 통해 사용할 간단한 도우미를 정의하겠습니다.

// a function that returns `then` if `value` is a promise, otherwise `null`
function getThen(value) {
  if (result && (typeof result === 'object' || typeof result === 'function')) {
    var then = value.then;
    if (typeof then === 'function') {
      return then;
    }
  }
  return null;
}

다음으로 발생할 수있는 각 변환을 고려해야합니다.

var PENDING = 0;
var FULFILLED = 1;
var REJECTED = 2;

function Promise() {

  // store state which can be PENDING, FULFILLED or REJECTED
  var state = PENDING;

  // store value once FULFILLED or REJECTED
  var value = null;

  // store sucess & failure handlers
  var handlers = [];

  function resolve(result) {
    try {
      var then = getThen(result);
      if (then) {
        doResolve(then.bind(result), resolve, reject)
        return
      }
      state = FULFILLED;
      value = result;
    } catch (e) {
      reject(e);
    }
  }

  function reject(error) {
    state = REJECTED;
    value = error;
  }
}

resolvePromise를 인수로받을 수있는 방법에 유의하십시오 . 그러나 Promise는 다른 Promise로는 결코 이행 될 수 없습니다. 그래서 우리는이 특별한 경우를 처리해야합니다.

약속은 한 번만 이행 / 거부 될 수 있습니다. 우리는 또한 제 3 자 Promise가 오작동 할 수있는 문제를 가지고 있으며, 우리 코드를 보호해야합니다. 이런 이유로, 나는 result.then(resolve, reject)내부에서 호출하지 않았습니다 resolve. 대신 별도의 함수로 분할했습니다.

/**
 * Take a potentially misbehaving resolver function and make sure
 * onFulfilled and onRejected are only called once.
 *
 * Makes no guarantees about asynchrony.
 */
function doResolve(fn, onFulfilled, onRejected) {
  var done = false;
  try {
    fn(function (value) {
      if (done) return
      done = true
      onFulfilled(value)
    }, function (reason) {
      if (done) return
      done = true
      onRejected(reason)
    })
  } catch (ex) {
    if (done) return
    done = true
    onRejected(ex)
  }
}

이제 완성 된 상태 시스템이 있지만 상태 변경을 관찰하거나 트리거 할 방법이 없습니다. 리졸버 함수를 전달하여 상태 변경을 트리거하는 방법을 추가하여 시작하겠습니다.

function Promise(fn) {
  if (typeof this !== 'object')
    throw new TypeError('Promises must be constructed via new');
  if (typeof fn !== 'function')
    throw new TypeError('fn must be a function');

  // store state which can be PENDING, FULFILLED or REJECTED
  var state = PENDING;

  // store value once FULFILLED or REJECTED
  var value = null;

  // store sucess & failure handlers
  var handlers = [];

  function resolve(result) {
    try {
      var then = getThen(result);
      if (then) {
        doResolve(then.bind(result), resolve, reject)
        return
      }
      state = FULFILLED;
      value = result;
    } catch (e) {
      reject(e);
    }
  }

  function reject(error) {
    state = REJECTED;
    value = error;
  }

  doResolve(fn, resolve, reject);
}

보시다시피 doResolve신뢰할 수없는 다른 해결 프로그램이 있기 때문에 재사용 합니다. 또는 여러 번 fn호출 할 수 있으며 오류가 발생할 수 있습니다. 우리는 이러한 모든 경우를 처리해야합니다 (그게 바로 그 일입니다 ).resolverejectdoResolve

우리는 지금 완성 된 상태 머신을 가지고 있지만, 우리는에 어떤 상태에 대한 정보를 노출하지 않았습니다. 추가하려고 할 수 있습니다 .done(onFulfilled, onRejected)처럼입니다 방법 .then은 약속을 반환하지 않습니다에 의해 발생 오류를 처리하지 않는다는 점을 제외 onFulfilled하고를 onRejected.

var PENDING = 0;
var FULFILLED = 1;
var REJECTED = 2;

function Promise(fn) {
  if (typeof this !== 'object')
    throw new TypeError('Promises must be constructed via new');
  if (typeof fn !== 'function')
    throw new TypeError('fn must be a function');

  // store state which can be PENDING, FULFILLED or REJECTED
  var state = PENDING;

  // store value once FULFILLED or REJECTED
  var value = null;

  // store sucess & failure handlers
  var handlers = [];

  function resolve(result) {
    try {
      var then = getThen(result);
      if (then) {
        doResolve(then.bind(result), resolve, reject)
        return
      }
      state = FULFILLED;
      value = result;
      handlers.forEach(handle);
      handlers = null;
    } catch (e) {
      reject(e);
    }
  }

  function reject(error) {
    state = REJECTED;
    value = error;
    handlers.forEach(handle);
    handlers = null;
  }

  function handle(handler) {
    if (state === PENDING) {
      handlers.push(handler);
    } else {
      if (state === FULFILLED && typeof handler.onFulfilled === 'function') {
        handler.onFulfilled(value);
      }
      if (state === REJECTED && typeof handler.onRejected === 'function') {
        handler.onRejected(value);
      }
    }
  }
  this.done = function (onFulfilled, onRejected) {
    setTimeout(function () { // ensure we are always asynchronous
      handle({
        onFulfilled: onFulfilled,
        onRejected: onRejected
      });
    }, 0);
  }

  doResolve(fn, resolve, reject);
}

.done약속이 이행 / 거부되기 전과 후에 호출되는 경우를 어떻게 처리해야하는지 주목하십시오 .

우리는 거의 완전한 promise 구현을 가지고 있지만, 이미 구현을 빌드 할 때 눈치 채셨 듯이 .thenPromise를 반환하는 메서드 가 필요합니다 .

우리는 이것을 쉽게 만들 수 있습니다 .done.

this.then = function (onFulfilled, onRejected) {
  var self = this;
  return new Promise(function (resolve, reject) {
    return self.done(function (result) {
      if (typeof onFulfilled === 'function') {
        try {
          return resolve(onFulfilled(result));
        } catch (ex) {
          return reject(ex);
        }
      } else {
        return resolve(result);
      }
    }, function (error) {
      if (typeof onRejected === 'function') {
        try {
          return resolve(onRejected(error));
        } catch (ex) {
          return reject(ex);
        }
      } else {
        return reject(error);
      }
    });
  });
}

resolve약속을 수락하고 해결되기를 기다리기 때문에 지금 무료로 어려움을 겪고있는 것을 얻는 방법을 여기에서 확인하십시오.

NB 이 Promise 구현을 테스트하지 않았습니다 (내가 아는 한 정확하지만). Promises / A + 테스트 스위트 ( https://github.com/promises-aplus/promises-tests ) 에 대해 빌드 한 모든 구현을 테스트해야 하며 Promises / A + 사양 ( https://github.com/promises)을 찾을 수도 있습니다. -aplus / promises-spec ) 알고리즘의 특정 부분에 대해 올바른 동작이 무엇인지 알아내는 데 유용합니다. 마지막 리소스로서 promise 는 Promise 사양을 최소한으로 구현 한 것입니다.


(전체 Promise 구현의 경우 아래로 스크롤).

코드의 몇 가지 문제

몇 가지 문제가 있지만 코드의 주요 실수는 then메서드에 주어진 인수를 가져 와서 약속 생성자에 인수로 전달하는 것입니다.

this.then=function(resolve,reject) {
    var nextPromise=new Aaa(resolve,true);
    // ...

두 인수 모두 콜백 함수이지만 서명이 다르며 완전히 다른 용도로 사용됩니다.

  • 약속 생성자에 대한 인수는 실행될 통화 백 기능입니다 즉시 , 동 기적 . 기능은 당신이 만드는 약속을 해결할 수있는 첫 번째 인수,로 전달됩니다.
  • then메서드에 대한 (첫 번째) 인수 는 기본 약속이 해결 될 때 나중에 비동기 적으로 실행되고 해결 된 값 이 인수로 전달 되는 콜백 함수입니다 .

생성자에 인수를 f 속성 으로 저장하는 코드에서도 차이점을 확인할 수 있습니다 . 이 두 가지가 있습니다.

t.thens[i].f(g);

... 여기서 g 는 해결 된 값이지만 다음도 마찬가지입니다.

this.f(this.resolve,this.reject); 

... 인수는 함수입니다. nextPromise 를 만들 때 실제로이 두 인수로 f먼저 호출 한 다음 나중에 g 인수로 f호출 합니다.

Promises / A + 준수 구현을 처음부터

Promise / A + 사양 의 요구 사항에 따라 자체 Promise 구현을 구축 할 수 있습니다 .

2.1 약속 상태

허용되는 상태 전환은 보류에서 이행으로, 보류에서 거부로 두 가지뿐입니다. 다른 전환은 가능하지 않아야하며 전환이 수행 된 후에는 약속 값 (또는 거부 이유)이 변경되어서는 안됩니다.

다음은 위의 제한 사항을 준수하는 간단한 구현입니다. 주석은 위 사양에서 번호가 매겨진 요구 사항을 참조합니다.

function MyPromise(executor) {
    this.state = 'pending';
    this.value = undefined;
    executor(this.resolve.bind(this), this.reject.bind(this));
}

// 2.1.1.1: provide only two ways to transition
MyPromise.prototype.resolve = function (value) {
    if (this.state !== 'pending') return; // 2.1.2.1, 2.1.3.1: cannot transition anymore
    this.state = 'fulfilled'; // 2.1.1.1: can transition
    this.value = value; // 2.1.2.2: must have a value
}

MyPromise.prototype.reject = function (reason) {
    if (this.state !== 'pending') return; // 2.1.2.1, 2.1.3.1: cannot transition anymore
    this.state = 'rejected'; // 2.1.1.1: can transition
    this.value = reason; // 2.1.3.2: must have a reason
}

물론 이것은 then약속의 핵심 인 방법을 제공하지 않습니다 .

2.2 then방법

이것이 사양의 핵심입니다. 위의 코드를 확장하여 프라 미스 then를 반환하고 적절한 then콜백 의 비동기 실행을 한 번만 제공하여 여러 then호출을 제공하고 예외를 거부로 전환 하는 등의 방법을 제공 할 수 있습니다.

따라서 다음 코드는 then메서드를 추가 하지만 broadcast상태 변경시 호출되어야하기 때문에 별도로 정의 된 함수 추가합니다. 여기에는 메서드 의 효과 then(Promise가 목록에 추가됨)뿐만 아니라 resolvereject방법 (상태 값 변화).

function MyPromise(executor) {
    this.state = 'pending';
    this.value = undefined;
    // A list of "clients" that need to be notified when a state
    //   change event occurs. These event-consumers are the promises
    //   that are returned by the calls to the `then` method.
    this.consumers = [];
    executor(this.resolve.bind(this), this.reject.bind(this));
}

// 2.1.1.1: provide only two ways to transition
MyPromise.prototype.resolve = function (value) {
    if (this.state !== 'pending') return; // 2.1.2.1, 2.1.3.1: cannot transition anymore
    this.state = 'fulfilled'; // 2.1.1.1: can transition
    this.value = value; // 2.1.2.2: must have a value
    this.broadcast();
}    

MyPromise.prototype.reject = function (reason) {
    if (this.state !== 'pending') return; // 2.1.2.1, 2.1.3.1: cannot transition anymore
    this.state = 'rejected'; // 2.1.1.1: can transition
    this.value = reason; // 2.1.3.2: must have a reason
    this.broadcast();
}    

// A promise’s then method accepts two arguments:
MyPromise.prototype.then = function(onFulfilled, onRejected) {
    var consumer = new MyPromise(function () {});
    // 2.2.1.1 ignore onFulfilled if not a function
    consumer.onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : null;
    // 2.2.1.2 ignore onRejected if not a function
    consumer.onRejected = typeof onRejected === 'function' ? onRejected : null;
    // 2.2.6.1, 2.2.6.2: .then() may be called multiple times on the same promise
    this.consumers.push(consumer);
    // It might be that the promise was already resolved... 
    this.broadcast();
    // 2.2.7: .then() must return a promise
    return consumer;
};

MyPromise.prototype.broadcast = function() {
    var promise = this;
    // 2.2.2.1, 2.2.2.2, 2.2.3.1, 2.2.3.2 called after promise is resolved
    if (this.state === 'pending') return;
    // 2.2.6.1, 2.2.6.2 all respective callbacks must execute
    var callbackName = this.state == 'fulfilled' ? 'onFulfilled' : 'onRejected';
    var resolver = this.state == 'fulfilled' ? 'resolve' : 'reject';
    // 2.2.4 onFulfilled/onRejected must be called asynchronously
    setTimeout(function() {
        // 2.2.6.1, 2.2.6.2 traverse in order, 2.2.2.3, 2.2.3.3 called only once
        promise.consumers.splice(0).forEach(function(consumer) {
            try {
                var callback = consumer[callbackName];
                // 2.2.1.1, 2.2.1.2 ignore callback if not a function, else
                // 2.2.5 call callback as plain function without context
                if (callback) {
                    // TODO: 2.2.7.1. For now we simply fulfill the promise:
                    consumer.resolve(callback(promise.value)); 
                } else {
                    // 2.2.7.3 resolve in same way as current promise
                    consumer[resolver](promise.value);
                }
            } catch (e) {
                // 2.2.7.2
                consumer.reject(e);
            };
        })
    });
};

이것은 TODO:의견에서 소위 약속 해결 절차를 호출해야 한다는 점을 제외하고 거의 모든 것을 다룹니다 .

2.3 약속 해결 절차

이것은 thenables (또는 promise) 인 값을 다르게 처리하는 프로 시저입니다. 값을있는 그대로 반환하는 대신 프로시 저는 then해당 값에 대해 메서드를 실행하고 해당 then콜백 에서받은 값으로 promise를 비동기 적으로 수행합니다 . 스펙에는 언급되어 있지 않지만, then방법뿐만 아니라 주요 약속이 이러한 값으로 해결 될 때 수행하는 것도 흥미 롭습니다 .

따라서 기존 resolve메소드는 원래 메소드를 호출하는이 "Promise Resolution Procedure"로 대체되어야합니다. 원래는 약속이 항상 이행 된대로 해결됨을 나타 내기 위해 "fulfill"이라고 할 수 있습니다.

function MyPromise(executor) {
    this.state = 'pending';
    this.value = undefined;
    // A list of "clients" that need to be notified when a state
    //   change event occurs. These event-consumers are the promises
    //   that are returned by the calls to the `then` method.
    this.consumers = [];
    executor(this.resolve.bind(this), this.reject.bind(this));
}

// 2.1.1.1: provide only two ways to transition
MyPromise.prototype.fulfill = function (value) {
    if (this.state !== 'pending') return; // 2.1.2.1, 2.1.3.1: cannot transition anymore
    this.state = 'fulfilled'; // 2.1.1.1: can transition
    this.value = value; // 2.1.2.2: must have a value
    this.broadcast();
}    

MyPromise.prototype.reject = function (reason) {
    if (this.state !== 'pending') return; // 2.1.2.1, 2.1.3.1: cannot transition anymore
    this.state = 'rejected'; // 2.1.1.1: can transition
    this.value = reason; // 2.1.3.2: must have a reason
    this.broadcast();
}    

// A promise’s then method accepts two arguments:
MyPromise.prototype.then = function(onFulfilled, onRejected) {
    var consumer = new MyPromise(function () {});
    // 2.2.1.1 ignore onFulfilled if not a function
    consumer.onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : null;
    // 2.2.1.2 ignore onRejected if not a function
    consumer.onRejected = typeof onRejected === 'function' ? onRejected : null;
    // 2.2.6.1, 2.2.6.2: .then() may be called multiple times on the same promise
    this.consumers.push(consumer);
    // It might be that the promise was already resolved... 
    this.broadcast();
    // 2.2.7: .then() must return a promise
    return consumer;
};

MyPromise.prototype.broadcast = function() {
    var promise = this;
    // 2.2.2.1, 2.2.2.2, 2.2.3.1, 2.2.3.2 called after promise is resolved
    if (this.state === 'pending') return;
    // 2.2.6.1, 2.2.6.2 all respective callbacks must execute
    var callbackName = this.state == 'fulfilled' ? 'onFulfilled' : 'onRejected';
    var resolver = this.state == 'fulfilled' ? 'resolve' : 'reject';
    // 2.2.4 onFulfilled/onRejected must be called asynchronously
    setTimeout(function() {
        // 2.2.6.1, 2.2.6.2 traverse in order, 2.2.2.3, 2.2.3.3 called only once
        promise.consumers.splice(0).forEach(function(consumer) {
            try {
                var callback = consumer[callbackName];
                // 2.2.1.1, 2.2.1.2 ignore callback if not a function, else
                // 2.2.5 call callback as plain function without context
                if (callback) {
                    // 2.2.7.1. execute the Promise Resolution Procedure:
                    consumer.resolve(callback(promise.value)); 
                } else {
                    // 2.2.7.3 resolve in same way as current promise
                    consumer[resolver](promise.value);
                }
            } catch (e) {
                // 2.2.7.2
                consumer.reject(e);
            };
        })
    });
};

// The Promise Resolution Procedure: will treat values that are thenables/promises
// and will eventually call either fulfill or reject/throw.
MyPromise.prototype.resolve = function(x) {
    var wasCalled, then;
    // 2.3.1
    if (this === x) {
        throw new TypeError('Circular reference: promise value is promise itself');
    }
    // 2.3.2
    if (x instanceof MyPromise) {
        // 2.3.2.1, 2.3.2.2, 2.3.2.3
        x.then(this.resolve.bind(this), this.reject.bind(this));
    } else if (x === Object(x)) { // 2.3.3
        try {
            // 2.3.3.1
            then = x.then;
            if (typeof then === 'function') {
                // 2.3.3.3
                then.call(x, function resolve(y) {
                    // 2.3.3.3.3 don't allow multiple calls
                    if (wasCalled) return;
                    wasCalled = true;
                    // 2.3.3.3.1 recurse
                    this.resolve(y);
                }.bind(this), function reject(reasonY) {
                    // 2.3.3.3.3 don't allow multiple calls
                    if (wasCalled) return;
                    wasCalled = true;
                    // 2.3.3.3.2
                    this.reject(reasonY);
                }.bind(this));
            } else {
                // 2.3.3.4
                this.fulfill(x);
            }
        } catch(e) {
            // 2.3.3.3.4.1 ignore if call was made
            if (wasCalled) return;
            // 2.3.3.2 or 2.3.3.3.4.2
            this.reject(e);
        }
    } else {
        // 2.3.4
        this.fulfill(x);
    }
}

이것은 이제 Promises / A +를 준수하며 적어도 테스트 스위트를 통과합니다. 그러나 Promise 개체는 너무 많은 메서드와 속성을 노출합니다.

then유일한 약속 개체

위에서 빌드 된 생성자는 Deferred 객체, 즉 노출 resolvereject메서드 유사한 것을 만듭니다 . 더 나쁜 것은 statusvalue속성이 쓰기 가능하다는 것입니다. 그래서, 보안되지 않은 이연 객체의 생성자로이를 생각하고, 그 기반으로 별도의 약속 생성자를 생성하는 것이 더 논리적 일 것입니다,하지만이 필요한 것을 노출하십시오 then방법가 액세스 할 수있는 생성자 콜백 resolvereject.

지연된 객체는 생성자 콜백 인수없이 수행 할 수 있으며 promise속성을 통해 순수한 promise 객체에 대한 액세스를 제공 할 수 있습니다.

function Deferred() {
    this.state = 'pending';
    this.value = undefined;
    this.consumers = [];
    this.promise = Object.create(MyPromise.prototype, {
        then: { value: this.then.bind(this) }
    });
}

// 2.1.1.1: provide only two ways to transition
Deferred.prototype.fulfill = function (value) {
    if (this.state !== 'pending') return; // 2.1.2.1, 2.1.3.1: cannot transition anymore
    this.state = 'fulfilled'; // 2.1.1.1: can transition
    this.value = value; // 2.1.2.2: must have a value
    this.broadcast();
}    

Deferred.prototype.reject = function (reason) {
    if (this.state !== 'pending') return; // 2.1.2.1, 2.1.3.1: cannot transition anymore
    this.state = 'rejected'; // 2.1.1.1: can transition
    this.value = reason; // 2.1.3.2: must have a reason
    this.broadcast();
}    

// A promise’s then method accepts two arguments:
Deferred.prototype.then = function(onFulfilled, onRejected) {
    var consumer = new Deferred();
    // 2.2.1.1 ignore onFulfilled if not a function
    consumer.onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : null;
    // 2.2.1.2 ignore onRejected if not a function
    consumer.onRejected = typeof onRejected === 'function' ? onRejected : null;
    // 2.2.6.1, 2.2.6.2: .then() may be called multiple times on the same promise
    this.consumers.push(consumer);
    // It might be that the promise was already resolved... 
    this.broadcast();
    // 2.2.7: .then() must return a promise
    return consumer;
};

Deferred.prototype.broadcast = function() {
    var promise = this;
    // 2.2.2.1, 2.2.2.2, 2.2.3.1, 2.2.3.2 called after promise is resolved
    if (this.state === 'pending') return;
    // 2.2.6.1, 2.2.6.2 all respective callbacks must execute
    var callbackName = this.state == 'fulfilled' ? 'onFulfilled' : 'onRejected';
    var resolver = this.state == 'fulfilled' ? 'resolve' : 'reject';
    // 2.2.4 onFulfilled/onRejected must be called asynchronously
    setTimeout(function() {
        // 2.2.6.1, 2.2.6.2 traverse in order, 2.2.2.3, 2.2.3.3 called only once
        promise.consumers.splice(0).forEach(function(consumer) {
            try {
                var callback = consumer[callbackName];
                // 2.2.1.1, 2.2.1.2 ignore callback if not a function, else
                // 2.2.5 call callback as plain function without context
                if (callback) {
                    // 2.2.7.1. execute the Promise Resolution Procedure:
                    consumer.resolve(callback(promise.value)); 
                } else {
                    // 2.2.7.3 resolve in same way as current promise
                    consumer[resolver](promise.value);
                }
            } catch (e) {
                // 2.2.7.2
                consumer.reject(e);
            };
        })
    });
};

// The Promise Resolution Procedure: will treat values that are thenables/promises
// and will eventually call either fulfill or reject/throw.
Deferred.prototype.resolve = function(x) {
    var wasCalled, then;
    // 2.3.1
    if (this.promise === x) {
        throw new TypeError('Circular reference: promise value is promise itself');
    }
    // 2.3.2
    if (x instanceof MyPromise) {
        // 2.3.2.1, 2.3.2.2, 2.3.2.3
        x.then(this.resolve.bind(this), this.reject.bind(this));
    } else if (x === Object(x)) { // 2.3.3
        try {
            // 2.3.3.1
            then = x.then;
            if (typeof then === 'function') {
                // 2.3.3.3
                then.call(x, function resolve(y) {
                    // 2.3.3.3.3 don't allow multiple calls
                    if (wasCalled) return;
                    wasCalled = true;
                    // 2.3.3.3.1 recurse
                    this.resolve(y);
                }.bind(this), function reject(reasonY) {
                    // 2.3.3.3.3 don't allow multiple calls
                    if (wasCalled) return;
                    wasCalled = true;
                    // 2.3.3.3.2
                    this.reject(reasonY);
                }.bind(this));
            } else {
                // 2.3.3.4
                this.fulfill(x);
            }
        } catch(e) {
            // 2.3.3.3.4.1 ignore if call was made
            if (wasCalled) return;
            // 2.3.3.2 or 2.3.3.3.4.2
            this.reject(e);
        }
    } else {
        // 2.3.4
        this.fulfill(x);
    }
}

function MyPromise(executor) {
    // A Promise is just a wrapper around a Deferred, exposing only the `then`
    // method, while `resolve` and `reject` are available in the constructor callback
    var df = new Deferred();
    // Provide access to the `resolve` and `reject` methods via the callback
    executor(df.resolve.bind(df), df.reject.bind(df));
    return df.promise;
}

이 코드에는 Deferred 메서드를 전용 함수로 만들고 유사한 코드를 더 짧은 코드 블록으로 병합하는 등 여러 가지 최적화가 가능하지만, 지금은 각 요구 사항이 어디에 적용되는지 매우 명확하게 보여줍니다.

즐거운 코딩입니다.


이 모든 것이 매우 복잡해 보입니다. 정말 간단한 재귀 솔루션이 있다고 생각합니다. 간결함을 위해 거부를 생략하겠습니다.하지만 체인을 중지한다는 점을 제외하면 해결과 거의 같습니다.

var MyPromise = function(callback) {
  this.callbacks = [];
  callback(this.resolve.bind(this));
 }

MyPromise.prototype.resolve = function(data) {
  var callback = this.callbacks.pop();
  var result =  callback(data);

  if (!result) return;

  if (result instanceof MyPromise) {
    var resolve = this.resolve.bind(this);
    return result.then(function(d) {
        return resolve(d);
    });
  }

  return this.resolve(result);

}

MyPromise.prototype.then = function(callback) {
  this.callbacks.unshift(callback);
  return this;
}

내 솔루션

function Promise(resolver){
    if(typeof resolver !== 'function') {
        throw new TypeError(`Promise resolver ${resolver} is not a function`)
    }
    this.state = 'pending'
    this.value = void 0
    try{
        resolver(this.resolve.bind(this), this.reject.bind(this))
    }catch(error){
        this.reject.call(this,error)
    }
}

Promise.prototype.resolve = function(value) {
    if(this.state !== 'pending') return
    this.value = value
    this.state = 'fulfilled'    
    setTimeout( () => {
        if(!this.onFulfilled) return
        this.onFulfilled(value)
    }, 0)
};

Promise.prototype.reject = function(reason){
    if(this.state !== 'pending') return
    this.value = reason
    this.state = 'rejected'
    setTimeout( () => {
        if(this.onRejected){
            this.onRejected(reason)
        }else{
            throw `Uncaught (in promise) ${reason}`
        }
    }, 0)
};

Promise.prototype.then = function(fulfilled, rejected){
    if ( typeof fulfilled !== 'function' && typeof rejected !== 'function' ) {
        return this;
    }
    if (typeof fulfilled !== 'function' && this.state === 'fulfilled' ||
        typeof rejected !== 'function' && this.state === 'rejected') {
        return this;
    }
    var self = this
    return new Promise( (resolve, reject) => {
        if(fulfilled && typeof fulfilled == "function"){
            var onFulfilled = function (){
                try{
                    var result = fulfilled(self.value)
                    if(result && typeof result.then === 'function'){
                        result.then(resolve, reject)
                    }else{
                        resolve(result)
                    }
                }catch(error){
                    reject(error)
                }
            }
            if(self.state === 'pending'){
                self.onFulfilled = onFulfilled
            }else if(self.state === 'fulfilled'){
                onFulfilled()
            }
        }
        if(rejected && typeof rejected == "function"){
            var onRejected = function (){
                try{
                    var result = rejected(self.value)
                    if(result && typeof result.then === 'function'){
                        result.then(resolve, reject)
                    }else{
                        resolve(result)
                    }
                }catch(error){
                    reject(error)
                }
            }
            if( self.state === 'pending'){
                self.onRejected = onRejected
            }else if(self.state === 'rejected'){
                onRejected()
            }
        }
    })
}

/*
 *  the methods don't in Promise/A+ 
 */
Promise.prototype.catch = function(onRejected){
    return this.then(null, onRejected)
}

Promise.all = function(iterable){
    if(typeof iterable[Symbol.iterator] !== 'function'){
        throw new TypeError(`${iterable[Symbol.iterator]} is not a function`)
    }
    // Array,TypedArray,String,arguments ==> length; Map,Set ==> size 
    let len = [...iterable].length, i = 0, counter = 0, res = [];
    return new Promise( (resolve, reject) => {
        for(let item of iterable){
            ( (i) => {
                Promise.resolve(item).then(function(value){
                    counter++
                    res[i] = value
                    if(counter == len){
                        resolve(res)
                    }
                },function(reason){
                    if(!called){
                        reject(reason)
                    }
                })
            })(i++)
        }
    })
}

Promise.race = function(iterable){
    if(typeof iterable[Symbol.iterator] !== 'function'){
        throw new TypeError(`${iterable[Symbol.iterator]} is not a function`)
    }
    return new Promise( (resolve,reject) => {
        for(let item of iterable){
            Promise.resolve(item).then(function(value){
                resolve(value)
            },function(reason){
                reject(reason)
            })
        }
    })
}

Promise.resolve = function(value){
    //if(value instanceof this) return value
    //if(value instanceof Promise) return value
    if(value.constructor !== Promise) return value
    return new Promise( (resolve,reject) => {
        if(value && typeof value === 'object' && typeof value.then === 'function'){
            resolve( value.then( v => v))
        }else{
            resolve(value)
        }
    })
}

Promise.reject = function(reason){
    return new Promise( (resolve,reject) => {
        reject(reason)
    })
}

참조 URL : https://stackoverflow.com/questions/23772801/basic-javascript-promise-implementation-attempt

반응형