> 웹 프론트엔드 > JS 튜토리얼 > 완전한 Promise를 구현하는 방법

완전한 Promise를 구현하는 방법

一个新手
풀어 주다: 2018-05-15 17:34:06
원래의
2457명이 탐색했습니다.

Promise를 사용해 보았지만 항상 이해가 되지 않는 느낌이 들었습니다. 많은 기사를 읽었지만 여전히 Promise의 구현 원리를 이해하지 못했습니다. 나중에 기사를 읽고 코드를 디버깅하고 마침내. 나는 천천히 그것에 대한 느낌을 얻었습니다. 아래에서 자신의 이해에 따라 Promise를 구현하십시오.

코드를 완전히 이해하려면 this 및 closure 의 의미를 이해해야 합니다.

Promise란 무엇입니까

간단히 말하면 Promise는 주로 비동기 콜백 문제를 해결하는 것입니다. Promise를 사용하여 비동기 콜백을 처리하면 코드 계층 구조가 명확해지고 이해하기 쉽고 유지 관리가 쉬워집니다. 그것의 주류 사양은 현재 주로 약속/A+. Promise의 사용법에 익숙하지 않다면 내 기사인 es6 학습 노트 5--promise를 참조하세요. 이해했다면 이 기사를 읽어보시면 큰 도움이 될 것입니다.

시작하기 전에 Promise의 역할을 경험해 볼 수 있는 Promise 적용 시나리오를 작성해 보겠습니다. Google과 Firefox는 현재 es6 Promise를 지원합니다. 비동기 작업을 시뮬레이션하기 위해 setTimeout을 사용합니다. 구체적인 코드는 다음과 같습니다.

function fn1(resolve, reject) {
    setTimeout(function() {
        console.log('步骤一:执行');
        resolve('1');
    },500);
}

function fn2(resolve, reject) {
    setTimeout(function() {
        console.log('步骤二:执行');
        resolve('2');
    },100);
}

new Promise(fn1).then(function(val){
    console.log(val);
    return new Promise(fn2);
}).then(function(val){
    console.log(val);
    return 33;
}).then(function(val){
    console.log(val);
});
로그인 후 복사

결국 우리가 작성한 Promise도 이 기능을 달성할 수 있습니다.

초기 구성

간단한 프롬시를 작성해 봅시다. Promise의 매개변수는 함수 fn입니다. 내부적으로 정의된 확인 메소드를 매개변수로 fn에 전달하고 fn을 호출합니다. 비동기 작업이 성공하면 Resolve 메소드가 호출된 후 그때 등록된 콜백 함수가 실행됩니다.

function Promise(fn){
  //需要一个成功时的回调
  var callback;
  //一个实例的方法,用来注册异步事件
  this.then = function(done){
    callback = done;
  }
  function resolve(){
    callback();
  }
  fn(resolve);
}
로그인 후 복사

체이닝 지원 추가

아래에 체이닝을 추가하세요. 성공적인 콜백 메서드는 저장되기 전에 배열로 변환되어야 합니다. 동시에 undefed가 출력되지 않도록solve 메소드에 매개변수를 추가합니다.

function Promise(fn) {
    var promise = this,
        value = null;
        promise._resolves = [];

    this.then = function (onFulfilled) {
        promise._resolves.push(onFulfilled);
        return this;
    };

    function resolve(value) {
        promise._resolves.forEach(function (callback) {
            callback(value);
        });
    }

    fn(resolve);
}
로그인 후 복사
  • promise = this이므로 어느 시점에서 이 포인터가 갑자기 변경되는 것에 대해 걱정할 필요가 없습니다.

  • then 메소드를 호출하고 콜백을 promise._resolvesqueue에 넣습니다.

  • Promise 객체를 생성하고 동시에 해당 fn을 호출하고 해결 메소드를 전달합니다. fn의 비동기 작업이 성공적으로 실행되면 그 후에는 Resolve가 호출됩니다. 즉, <code><span style="color:#000000">promise._resolves</span>列中的回调;

  • <span style="color:#000000">resolve 方法</span>接收一个参数,即异步操作返回的结果,方便传值。

  • then方法中的 return this 实现了链式调用。

但是,目前的 Promise 还存在一些问题,如果我传入的是一个不包含异步操作的函数,resolve就会先于 then 执行,也就是说 promise._resolves 是一个空数组。

为了解决这个问题,我们可以在 resolve 中添加 setTimeout,来将 resolvepromise._resolves 팀의

열에 있는 콜백이 실행됩니다. <h2>resolve method</h2>는 비동기 작업에서 반환된 결과인 매개변수

를 받습니다. 값을 쉽게 전달할 수 있습니다.

then 메소드의 return this은 체인 호출을 구현합니다.

그러나 현재 Promise에는 여전히 몇 가지 문제가 있습니다. 비동기 작업이 포함되지 않은 함수를 전달하면 그 전에 해결이 실행되므로

promise._resolves 배열이 비어 있습니다. .

이 문제를 해결하기 위해 resolve의 콜백 실행 로직을 JS 작업 대기열 끝에 배치하기 위해 resolve에 setTimeout을 추가할 수 있습니다.

    function resolve(value) {
        setTimeout(function() {
            promise._resolves.forEach(function (callback) {
                callback(value);
            });
        },0);
    }
로그인 후 복사

상태 소개🎜🎜Promise의 기본 분석 여기에는 문제가 있습니다. Promise 비동기 작업이 성공한 경우 그때까지 등록된 콜백은 다시는 실행되지 않으며 이는 우리의 기대와 일치하지 않습니다. 🎜🎜이 문장을 잘 이해하지 못하는 경우, 예를 들어 메시지를 남겨주세요. 하지만 개인적으로 그때 등록된 콜백은 해결이 실행되기 전에 배열에 추가되므로 실행되지 않는 일은 없을 것이라고 생각합니다. 🎜🎜위 단계에 따라 상태를 소개하세요. 🎜
<p style="margin-bottom: 7px;">function Promise(fn) {<br/>    var promise = this,<br/>        value = null;<br/>        promise._resolves = [];<br/>        promise._status = &#39;PENDING&#39;;<br/><br/>    this.then = function (onFulfilled) {<br/>        if (promise._status === &#39;PENDING&#39;) {<br/>            promise._resolves.push(onFulfilled);<br/>            return this;<br/>        }<br/>        onFulfilled(value);<br/>        return this;<br/>    };<br/>    function resolve(value) {<br/>        setTimeout(function(){<br/>            promise._status = "FULFILLED";<br/>            promise._resolves.forEach(function (callback) {<br/>                callback(value);<br/>            })<br/>        },0);<br/>    }<br/><br/>    fn(resolve);<br/>}<br/></p>
로그인 후 복사
🎜각 Promise에는 보류, 이행, 거부라는 세 가지 상호 배타적인 상태가 있습니다. Promise 객체의 상태가 변경되는 경우는 보류에서 이행으로, 보류에서 거부로 두 가지뿐입니다. 이 두 가지 상황이 발생하는 한 상태는 굳어져 다시는 변하지 않고 이 결과를 유지할 것입니다. 이미 변경이 발생한 경우에도 Promise 객체에 콜백 함수를 추가하면 즉시 결과를 얻을 수 있습니다. 이는 이벤트와는 전혀 다른 이벤트의 특징은 놓치고 다시 들으면 결과가 나오지 않는다는 점입니다. 🎜🎜비동기 결과 전달 추가🎜🎜현재 작성 방법은 비동기 반환 결과 전달을 고려하지 않습니다. 결과 전달을 추가해 보겠습니다.🎜
    function resolve(value) {
        setTimeout(function(){
            promise._status = "FULFILLED";
            promise._resolves.forEach(function (callback) {
                value = callback(value);
            })
        },0);
    }
로그인 후 복사
🎜Serial Promise🎜🎜Serial Promise는 현재 Promise가 이행 상태에 도달한다는 의미입니다. 그러면 다음 약속(다음 약속)이 시작됩니다. 예를 들어 먼저 ajax를 사용하여 백그라운드에서 사용자 데이터를 얻은 다음 이 데이터를 기반으로 다른 데이터를 얻습니다. 🎜🎜여기에서는 주로 then 메소드를 수정합니다: 🎜
    this.then = function (onFulfilled) {
        return new Promise(function(resolve) {
            function handle(value) {
                var ret = isFunction(onFulfilled) && onFulfilled(value) || value;
                resolve(ret);
            }
            if (promise._status === &#39;PENDING&#39;) {
                promise._resolves.push(handle);
            } else if(promise._status === FULFILLED){
                handle(value);
            }
        })
        
    };
로그인 후 복사
🎜then 메소드에는 많은 변경이 필요합니다. 여기서 설명하겠습니다. 🎜
  • 注意的是,new Promise() 中匿名函数中的 promise (promise._resolves中的 promise)指向的都是上一个 promise 对象, 而不是当前这个刚刚创建的。

  • 首先我们返回的是新的一个promise对象,因为是同类型,所以链式仍然可以实现。

  • 其次,我们添加了一个 handle 函数,handle 函数对上一个 promise 的 then 中回调进行了处理,并且调用了当前的 promise 中的 resolve 方法。

  • 接着将 handle 函数添加到 上一个promise 的 promise._resolves 中,当异步操作成功后就会执行 handle 函数,这样就可以 执行 当前 promise 对象的回调方法。我们的目的就达到了。

有些人在这里可能会有点犯晕,有必要对执行过程分析一下,具体参看以下代码:

new Promise(fn1).then(fn2).then(fn3)})
로그인 후 복사

fn1, fn2, fn3的函数具体可参看最前面的定义。

  1. 首先我们创建了一个 Promise 实例,这里叫做 promise1;接着会运行 fn1(resolve);

  2. 但是 fn1 中有一个 setTimeout 函数,于是就会先跳过这一部分,运行后面的第一个 then 方法;

  3. then 返回一个新的对象 promise2, promise2 对象的 resolve方法和 then 方法的中回调函数 fn2 都被封装在 handle 中, 然后 handle 被添加到   promise1._resolves 数组中。

  4. 接着运行第二个 then 方法,同样返回一个新的对象 promise3, 包含 promise3 的 resolve 方法和回调函数 fn3 的 handle 方法被添加到 promise2._resolves 数组中。

  5. 到此两个 then 运行结束。 setTimeout 中的延迟时间一到,就会调用 promise1的 resolve方法。

  6. resolve 方法的执行,会调用 promise1._resolves 数组中的回调,之前我们添加的 handle 方法就会被执行; 也就是 fn2 和 promsie2 的 resolve 方法,都被调用了。

  7. 以此类推,fn3 会和promise3 的 resolve 方法 一起执行,因为后面没有 then 方法了,promise3._resolves 数组是空的

  8. 至此所有回调执行结束

但这里还存在一个问题,就是我们的 then 里面函数不能对 Promise 对象进行处理。这里我们需要再次对 then 进行修改,使其能够处理 promise 对象。

this.then = function (onFulfilled) {
        return new Promise(function(resolve) {
            function handle(value) {
                var ret = typeof onFulfilled === &#39;function&#39; && onFulfilled(value) || value;
                if( ret && typeof ret [&#39;then&#39;] == &#39;function&#39;){
                    ret.then(function(value){
                       resolve(value);
                    });
                } else {
                    resolve(ret);
                }
            }
            if (promise._status === &#39;PENDING&#39;) {
                promise._resolves.push(handle);
            } else if(promise._status === FULFILLED){
                handle(value);
            }
        })
        
    };
로그인 후 복사

在 then 方法里面,我们对 ret 进行了判断,如果是一个 promise 对象,就会调用其 then 方法,形成一个嵌套,直到其不是promise对象为止。同时 在 then 方法中我们添加了调用 resolve 方法,这样链式得以维持。

失败处理

异步操作不可能都成功,在异步操作失败时,标记其状态为 rejected,并执行注册的失败回调。

有了之前处理 fulfilled 状态的经验,支持错误处理变得很容易。毫无疑问的是,在注册回调、处理状态变更上都要加入新的逻辑:

    this.then = function (onFulfilled, onRejected) {
        return new Promise(function(resolve, reject) {
            function handle(value) {
                .......
            }
            function errback(reason){
                reason = isFunction(onRejected) && onRejected(reason) || reason;
                reject(reason);
            }
            if (promise._status === &#39;PENDING&#39;) {
                promise._resolves.push(handle);
                promise._rejects.push(errback);
            } else if(promise._status === &#39;FULFILLED&#39;){
                handle(value);
            } else if(promise._status === &#39;REJECTED&#39;) {
              errback(promise._reason);
        }
        })
        
    };

    function reject(value) {
        setTimeout(function(){
            promise._status = "REJECTED";
            promise._rejects.forEach(function (callback) {
                promise._reason = callback( value);
            })
        },0);
    }
로그인 후 복사

添加Promise.all方法

Promise.all 可以接收一个元素为 Promise 对象的数组作为参数,当这个数组里面所有的 Promise 对象都变为 resolve 时,该方法才会返回。

具体代码如下:

Promise.all = function(promises){
    if (!Array.isArray(promises)) {
        throw new TypeError(&#39;You must pass an array to all.&#39;);
      }
    // 返回一个promise 实例
      return new Promise(function(resolve,reject){
          var i = 0,
              result = [],
              len = promises.length,
              count = len;

      // 每一个 promise 执行成功后,就会调用一次 resolve 函数
          function resolver(index) {
            return function(value) {
               resolveAll(index, value);
            };
         }

        function rejecter(reason){
            reject(reason);
        }

        function resolveAll(index,value){
       // 存储每一个promise的参数
            result[index] = value;
       // 等于0 表明所有的promise 都已经运行完成,执行resolve函数
            if( --count == 0){
                resolve(result)
            }
        }
      // 依次循环执行每个promise
          for (; i < len; i++) {
         // 若有一个失败,就执行rejecter函数 
              promises[i].then(resolver(i),rejecter);
          }
      });
}
로그인 후 복사

Promise.all会返回一个 Promise 实例,该实例直到参数中的所有的 promise 都执行成功,才会执行成功回调,一个失败就会执行失败回调。

日常开发中经常会遇到这样的需求,在不同的接口请求数据然后拼合成自己所需的数据,通常这些接口之间没有关联(例如不需要前一个接口的数据作为后一个接口的参数),这个时候 Promise.all 方法就可以派上用场了。

添加Promise.race方法

该函数和 Promise.all 相类似,它同样接收一个数组,不同的是只要该数组中的任意一个 Promise 对象的状态发生变化(无论是 resolve 还是 reject)该方法都会返回。我们只需要对 Promise.all 方法稍加修改就可以了。

Promise.race = function(promises){    if (!Array.isArray(promises)) {        throw new TypeError(&#39;You must pass an array to race.&#39;);
    }    return Promise(function(resolve,reject){        var i = 0,
            len = promises.length;        function resolver(value) {
            resolve(value);
        }        function rejecter(reason){
            reject(reason);
        }        for (; i < len; i++) {
            promises[i].then(resolver,rejecter);
        }
    });
}
로그인 후 복사

代码中没有类似一个 resolveAll 的函数,因为我们不需要等待所有的 promise 对象状态都发生变化,只要一个就可以了。

添加其他API以及封装函数

到这里,Promise 的主要API都已经完成了,另外我们在添加一些比较常见的方法。也对一些可能出现的错误进行了处理,最后对其进行封装。

完整的代码如下:

(function(window,undefined){

// resolve 和 reject 最终都会调用该函数
var final = function(status,value){
    var promise = this, fn, st;
        
    if(promise._status !== &#39;PENDING&#39;) return;
    
    // 所以的执行都是异步调用,保证then是先执行的
    setTimeout(function(){
        promise._status = status;
        st = promise._status === &#39;FULFILLED&#39;
        queue = promise[st ? &#39;_resolves&#39; : &#39;_rejects&#39;];

        while(fn = queue.shift()) {
            value = fn.call(promise, value) || value;
        }

        promise[st ? &#39;_value&#39; : &#39;_reason&#39;] = value;
        promise[&#39;_resolves&#39;] = promise[&#39;_rejects&#39;] = undefined;
    });
}


//参数是一个函数,内部提供两个函数作为该函数的参数,分别是resolve 和 reject
var Promise = function(resolver){
    if (!(typeof resolver === &#39;function&#39; ))
        throw new TypeError(&#39;You must pass a resolver function as the first argument to the promise constructor&#39;);
    //如果不是promise实例,就new一个
    if(!(this instanceof Promise)) return new Promise(resolver);

    var promise = this;
    promise._value;
    promise._reason;
    promise._status = &#39;PENDING&#39;;
    //存储状态
    promise._resolves = [];
    promise._rejects = [];
    
    //
    var resolve = function(value) {
        //由於apply參數是數組
        final.apply(promise,[&#39;FULFILLED&#39;].concat([value]));
    }

    var reject = function(reason){
        final.apply(promise,[&#39;REJECTED&#39;].concat([reason]));
    }
    
    resolver(resolve,reject);
}

Promise.prototype.then = function(onFulfilled,onRejected){
    var promise = this;
    // 每次返回一个promise,保证是可thenable的
    return new Promise(function(resolve,reject){
        
        function handle(value) {
            // 這一步很關鍵,只有這樣才可以將值傳遞給下一個resolve
            var ret = typeof onFulfilled === &#39;function&#39; && onFulfilled(value) || value;

            //判断是不是promise 对象
            if (ret && typeof ret [&#39;then&#39;] == &#39;function&#39;) {
                ret.then(function(value) {
                    resolve(value);
                }, function(reason) {
                    reject(reason);
                });
            } else {
                resolve(ret);
            }
        }

        function errback(reason){
            reason = typeof onRejected === &#39;function&#39; && onRejected(reason) || reason;
            reject(reason);
        }

        if(promise._status === &#39;PENDING&#39;){
            promise._resolves.push(handle);
            promise._rejects.push(errback);
        }else if(promise._status === FULFILLED){ // 状态改变后的then操作,立刻执行
            callback(promise._value);
        }else if(promise._status === REJECTED){
            errback(promise._reason);
        }
    });
}

Promise.prototype.catch = function(onRejected){
    return this.then(undefined, onRejected)
}

Promise.prototype.delay = function(ms,value){
    return this.then(function(ori){
        return Promise.delay(ms,value || ori);
    })
}

Promise.delay = function(ms,value){
    return new Promise(function(resolve,reject){
        setTimeout(function(){
            resolve(value);
            console.log(&#39;1&#39;);
        },ms);
    })
}

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

Promise.reject = function(arg){
    return Promise(function(resolve,reject){
        reject(arg)
    })
}

Promise.all = function(promises){
    if (!Array.isArray(promises)) {
        throw new TypeError(&#39;You must pass an array to all.&#39;);
    }
    return Promise(function(resolve,reject){
        var i = 0,
            result = [],
            len = promises.length,
            count = len
            
        //这里与race中的函数相比,多了一层嵌套,要传入index
        function resolver(index) {
          return function(value) {
            resolveAll(index, value);
          };
        }

        function rejecter(reason){
            reject(reason);
        }

        function resolveAll(index,value){
            result[index] = value;
            if( --count == 0){
                resolve(result)
            }
        }

        for (; i < len; i++) {
            promises[i].then(resolver(i),rejecter);
        }
    });
}

Promise.race = function(promises){
    if (!Array.isArray(promises)) {
        throw new TypeError(&#39;You must pass an array to race.&#39;);
    }
    return Promise(function(resolve,reject){
        var i = 0,
            len = promises.length;

        function resolver(value) {
            resolve(value);
        }

        function rejecter(reason){
            reject(reason);
        }

        for (; i < len; i++) {
            promises[i].then(resolver,rejecter);
        }
    });
}

window.Promise = Promise;

})(window);
로그인 후 복사

代码写完了,总要写几个实例看看效果啊,具体看下面的测试代码:

var getData100 = function(){
    return new Promise(function(resolve,reject){
        setTimeout(function(){
            resolve(&#39;100ms&#39;);
        },1000);
    });
}

var getData200 = function(){
    return new Promise(function(resolve,reject){
        setTimeout(function(){
            resolve(&#39;200ms&#39;);
        },2000);
    });
}
var getData300 = function(){
    return new Promise(function(resolve,reject){
        setTimeout(function(){
            reject(&#39;reject&#39;);
        },3000);
    });
}

getData100().then(function(data){
    console.log(data);      // 100ms
    return getData200();
}).then(function(data){
    console.log(data);      // 200ms
    return getData300();
}).then(function(data){
    console.log(data);      
}, function(data){
    console.log(data);      // &#39;reject&#39;
});

Promise.all([getData100(), getData200()]).then(function(data){
    console.log(data);      // [ "100ms", "200ms" ]
});

Promise.race([getData100(), getData200(), getData300()]).then(function(data){
    console.log(data);      // 100ms
});
Promise.resolve(&#39;resolve&#39;).then(function(data){
    console.log(data);      //&#39;resolve&#39;
})
Promise.reject(&#39;reject函数&#39;).then(function(data){
    console.log(data);
}, function(data){
    console.log(data);     //&#39;reject函数&#39;
})
로그인 후 복사

위 내용은 완전한 Promise를 구현하는 방법의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

관련 라벨:
원천:php.cn
본 웹사이트의 성명
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.
인기 튜토리얼
더>
최신 다운로드
더>
웹 효과
웹사이트 소스 코드
웹사이트 자료
프론트엔드 템플릿