JavaScript 프로그래밍에서 Promise 사용에 대한 자세한 해석_기본 지식

WBOY
풀어 주다: 2016-05-16 15:48:50
원래의
1122명이 탐색했습니다.

약속 핵심설명

Promise에는 이미 자체 사양이 있지만 현재의 다양한 Promise 라이브러리는 Promise의 구현 세부 사항에 차이가 있으며 일부 API는 의미가 완전히 다릅니다. 하지만 Promise의 핵심 내용은 동일합니다. 바로 then 메소드입니다. 관련 용어로 Promise는 특정 동작을 트리거할 수 있는 then 메서드가 있는 개체나 함수를 나타냅니다.

Promise는 다양한 방식으로 구현될 수 있으므로 Promise 핵심 설명에서는 특정 구현 코드를 논의하지 않습니다.

먼저 Promise의 핵심 설명을 읽어보세요. 즉, 작성해야 할 결과입니다. 이 결과를 참조하여 코드로 작성하는 방법을 생각해 보세요.
시작하기: Promise를 이런 식으로 이해하세요

Promise가 해결하는 문제가 무엇인지 기억하시나요? 콜백. 예를 들어, doMission1() 함수는 첫 번째 작업을 나타냅니다. 이제 이 작업이 완료된 후 다음 작업인 doMission2()를 수행하려고 합니다.

먼저 일반적인 콜백 패턴을 살펴보겠습니다. doMission1()이 말했습니다: "이 작업을 수행하려면 doMission2()를 알려주십시오. 완료되면 제가 대신 전화해 드리겠습니다.

doMission1(doMission2);

로그인 후 복사

약속 모드는 어떻습니까? doMission1()에 대해 다음과 같이 말했습니다. "아니요, 제어권은 저에게 있습니다. 변경해야 합니다. 먼저 나에게 특별한 것을 반환한 다음 이 것을 사용하여 다음 것을 준비할 것입니다." 다음과 같습니다:

doMission1().then(doMission2);

로그인 후 복사

Promise는 콜백 모드의 마스터-슬레이브 관계를 변경하는 것을 볼 수 있습니다(뒤집어 마스터가 됩니다!). 여러 이벤트의 프로세스 관계가 여러 이벤트에 분산되지 않고 주요 도로에 집중될 수 있습니다. 기능).

그럼 어떻게 변환하나요? doMission1()의 코드가 다음과 같다고 가정하고 가장 간단한 경우부터 시작해 보겠습니다.

function doMission1(callback){
  var value = 1;
  callback(value);
}

로그인 후 복사

그러면 다음과 같이 바뀔 수 있습니다.

function doMission1(){
  return {
    then: function(callback){
      var value = 1;
      callback(value);
    }
  };
}

로그인 후 복사

이렇게 하면 변환이 완료됩니다. 실제로 유용한 변환은 아니지만 여기서는 실제로 Promise의 가장 중요한 구현 지점을 다루었습니다. 즉, Promise는 then 메서드를 사용하여 반환 값을 객체로 변환합니다.
고급: Q의 디자인 여정
연기부터 시작

design/q0.js는 Q 초기 형성의 첫 번째 단계입니다. Promise 생성을 위해 defer라는 유틸리티 함수를 생성합니다.

var defer = function () {
  var pending = [], value;
  return {
    resolve: function (_value) {
      value = _value;
      for (var i = 0, ii = pending.length; i < ii; i++) {
        var callback = pending[i];
        callback(value);
      }
      pending = undefined;
    },
    then: function (callback) {
      if (pending) {
        pending.push(callback);
      } else {
        callback(value);
      }
    }
  }
};

로그인 후 복사

이 소스 코드에서 볼 수 있듯이 defer()를 실행하면 두 가지 메소드(resolve와 then)가 포함된 객체를 얻게 됩니다. jQuery의 Deferred(resolve 및 then)를 기억해 보세요. 이 두 가지 방법은 비슷한 효과를 갖습니다. 그러면 보류 상태를 참조하여 대기 상태이면 콜백이 저장(푸시)되고, 그렇지 않으면 즉시 콜백이 호출됩니다. 해결은 Promise를 확인하고 값을 업데이트하며 저장된 모든 콜백을 동시에 실행합니다. defer 사용법의 예시는 다음과 같습니다.

var oneOneSecondLater = function () {
  var result = defer();
  setTimeout(function () {
    result.resolve(1);
  }, 1000);
  return result;
};

로그인 후 복사

oneOneSecondLater().then(콜백);

여기서 oneOneSecondLater()에는 비동기 콘텐츠(setTimeout)가 포함되어 있지만 여기서는 defer()에 의해 생성된 객체를 즉시 반환한 다음 비동기 끝에서 객체의 해결 메서드를 호출합니다(상위 값 또는 다른 값 사용). 단어 결과).

이 시점에서 위 코드에는 문제가 있습니다. 해결이 여러 번 실행될 수 있다는 것입니다. 따라서 결의가 한 번만 유효하도록 결의에 상태 판단을 추가해야 합니다. Q 다음 단계의 design/q1.js입니다(차이 부분만):

resolve: function (_value) {
  if (pending) {
    value = _value;
    for (var i = 0, ii = pending.length; i < ii; i++) {
      var callback = pending[i];
      callback(value);
    }
    pending = undefined;
  } else {
    throw new Error("A promise can only be resolved once.");
  }
}

로그인 후 복사

두 번째 이후 호출에서는 이렇게 오류가 뜰 수도 있고, 그냥 무시해도 됩니다.
별도의 연기와 약속

이전 구현에서 defer에 의해 생성된 객체에는 then 메서드와 해결 메서드가 모두 있습니다. 정의에 따르면 Promise는 상태를 변경하는 Promise를 트리거하는 then 메서드에 관심이 있습니다. 따라서 Q는 다음으로 promise를 then 메서드로 분리하고 defer를 해결로 분리하고 각각을 독립적으로 사용합니다. 이는 각자의 책임을 명확히 하고 특정 권한만 남겨두는 것과 같습니다. 이렇게 하면 코드 논리가 더 명확해지고 조정하기 쉬워집니다. design/q3.js를 참조하세요: (q2는 여기에서 건너뛰었습니다)

var isPromise = function (value) {
  return value && typeof value.then === "function";
};

var defer = function () {
  var pending = [], value;
  return {
    resolve: function (_value) {
      if (pending) {
        value = _value;
        for (var i = 0, ii = pending.length; i < ii; i++) {
          var callback = pending[i];
          callback(value);
        }
        pending = undefined;
      }
    },
    promise: {
      then: function (callback) {
        if (pending) {
          pending.push(callback);
        } else {
          callback(value);
        }
      }
    }
  };
};

로그인 후 복사

q1을 꼼꼼히 비교해보면 그 차이가 매우 작다는 것을 알 수 있습니다. 한편으로는 오류가 더 이상 발생하지 않습니다(두 번째 및 더 많은 해결을 직접 무시하는 대신). 반면에 then 메서드는 promise라는 개체로 이동됩니다. 이 시점에서 defer()(defer라고 부르겠습니다)를 실행하여 얻은 객체는 다른 객체를 가리키는 promise 속성과 해결 메서드를 갖게 됩니다. 이 다른 객체는 then 메소드만 있는 promise입니다. 이것으로 분리가 완료됩니다.

또한 앞에 isPromise() 함수가 있는데, 객체에 then 메서드(오리 타이핑 판단 방법)가 있는지 여부로 해당 객체가 Promise인지 여부를 결정합니다. 분리된 Promise를 올바르게 사용하고 처리하려면 이와 같이 Promise를 다른 값과 구별해야 합니다.
Promise 캐스케이드 구현

다음 단계가 매우 중요합니다. 3분기까지는 구현된 Promise를 계단식으로 배열할 수 없습니다. 하지만 여러분에게 익숙한 Promise는 다음과 같은 구문을 지원해야 합니다.

promise.then(step1).then(step2);

로그인 후 복사

以上过程可以理解为,promise将可以创造新的promise,且取自旧的promise的值(前面代码中的value)。要实现then的级联,需要做到一些事情:

  • then方法必须返回promise。
  • 这个返回的promise必须用传递给then方法的回调运行后的返回结果,来设置自己的值。
  • 传递给then方法的回调,必须返回一个promise或值。

design/q4.js中,为了实现这一点,新增了一个工具函数ref:

var ref = function (value) {
  if (value && typeof value.then === "function")
    return value;
  return {
    then: function (callback) {
      return ref(callback(value));
    }
  };
};

로그인 후 복사

这是在着手处理与promise关联的value。这个工具函数将对任一个value值做一次包装,如果是一个promise,则什么也不做,如果不是promise,则将它包装成一个promise。注意这里有一个递归,它确保包装成的promise可以使用then方法级联。为了帮助理解它,下面是一个使用的例子:

ref("step1").then(function(value){
  console.log(value); // "step1"
  return 15;
}).then(function(value){
  console.log(value); // 15
});

로그인 후 복사

你可以看到value是怎样传递的,promise级联需要做到的也是如此。

design/q4.js通过结合使用这个ref函数,将原来的defer转变为可级联的形式:

var defer = function () {
  var pending = [], value;
  return {
    resolve: function (_value) {
      if (pending) {
        value = ref(_value); // values wrapped in a promise
        for (var i = 0, ii = pending.length; i < ii; i++) {
          var callback = pending[i];
          value.then(callback); // then called instead
        }
        pending = undefined;
      }
    },
    promise: {
      then: function (_callback) {
        var result = defer();
        // callback is wrapped so that its return
        // value is captured and used to resolve the promise
        // that "then" returns
        var callback = function (value) {
          result.resolve(_callback(value));
        };
        if (pending) {
          pending.push(callback);
        } else {
          value.then(callback);
        }
        return result.promise;
      }
    }
  };
};

로그인 후 복사

原来callback(value)的形式,都修改为value.then(callback)。这个修改后效果其实和原来相同,只是因为value变成了promise包装的类型,会需要这样调用。

then方法有了较多变动,会先新生成一个defer,并在结尾处返回这个defer的promise。请注意,callback不再是直接取用传递给then的那个,而是在此基础之上增加一层,并把新生成的defer的resolve方法放置在此。此处可以理解为,then方法将返回一个新生成的promise,因此需要把promise的resolve也预留好,在旧的promise的resolve运行后,新的promise的resolve也会随之运行。这样才能像管道一样,让事件按照then连接的内容,一层一层传递下去。
加入错误处理

promise的then方法应该可以包含两个参数,分别是肯定和否定状态的处理函数(onFulfilled与onRejected)。前面我们实现的promise还只能转变为肯定状态,所以,接下来应该加入否定状态部分。

请注意,promise的then方法的两个参数,都是可选参数。design/q6.js(q5也跳过)加入了工具函数reject来帮助实现promise的否定状态:

var reject = function (reason) {
  return {
    then: function (callback, errback) {
      return ref(errback(reason));
    }
  };
};

로그인 후 복사

它和ref的主要区别是,它返回的对象的then方法,只会取第二个参数的errback来运行。design/q6.js的其余部分是:

var defer = function () {
  var pending = [], value;
  return {
    resolve: function (_value) {
      if (pending) {
        value = ref(_value);
        for (var i = 0, ii = pending.length; i < ii; i++) {
          value.then.apply(value, pending[i]);
        }
        pending = undefined;
      }
    },
    promise: {
      then: function (_callback, _errback) {
        var result = defer();
        // provide default callbacks and errbacks
        _callback = _callback || function (value) {
          // by default, forward fulfillment
          return value;
        };
        _errback = _errback || function (reason) {
          // by default, forward rejection
          return reject(reason);
        };
        var callback = function (value) {
          result.resolve(_callback(value));
        };
        var errback = function (reason) {
          result.resolve(_errback(reason));
        };
        if (pending) {
          pending.push([callback, errback]);
        } else {
          value.then(callback, errback);
        }
        return result.promise;
      }
    }
  };
};

로그인 후 복사

这里的主要改动是,将数组pending只保存单个回调的形式,改为同时保存肯定和否定的两种回调的形式。而且,在then中定义了默认的肯定和否定回调,使得then方法满足了promise的2个可选参数的要求。

你也许注意到defer中还是只有一个resolve方法,而没有类似jQuery的reject。那么,错误处理要如何触发呢?请看这个例子:

var defer1 = defer(),
promise1 = defer1.promise;
promise1.then(function(value){
  console.log("1: value = ", value);
  return reject("error happens"); 
}).then(function(value){
  console.log("2: value = ", value);
}).then(null, function(reason){
  console.log("3: reason = ", reason);
});
defer1.resolve(10);

// Result:
// 1: value = 10
// 3: reason = error happens

로그인 후 복사

可以看出,每一个传递给then方法的返回值是很重要的,它将决定下一个then方法的调用结果。而如果像上面这样返回工具函数reject生成的对象,就会触发错误处理。
融入异步

终于到了最后的design/q7.js。直到前面的q6,还存在一个问题,就是then方法运行的时候,可能是同步的,也可能是异步的,这取决于传递给then的函数(例如直接返回一个值,就是同步,返回一个其他的promise,就可以是异步)。这种不确定性可能带来潜在的问题。因此,Q的后面这一步,是确保将所有then转变为异步。

design/q7.js定义了另一个工具函数enqueue:

var enqueue = function (callback) {
  //process.nextTick(callback); // NodeJS
  setTimeout(callback, 1); // Na&#63;ve browser solution
};

로그인 후 복사

显然,这个工具函数会将任意函数推迟到下一个事件队列运行。

design/q7.js其他的修改点是(只显示修改部分):

var ref = function (value) {
  // ...
  return {
    then: function (callback) {
      var result = defer();
      // XXX
      enqueue(function () {
        result.resolve(callback(value));
      });
      return result.promise;
    }
  };
};

var reject = function (reason) {
  return {
    then: function (callback, errback) {
      var result = defer();
      // XXX
      enqueue(function () {
        result.resolve(errback(reason));
      });
      return result.promise;
    }
  };
};

var defer = function () {
  var pending = [], value;
  return {
    resolve: function (_value) {
      // ...
          enqueue(function () {
            value.then.apply(value, pending[i]);
          });
      // ...
    },
    promise: {
      then: function (_callback, _errback) {
          // ...
          enqueue(function () {
            value.then(callback, errback);
          });
          // ...
      }
    }
  };
};

로그인 후 복사

即把原来的value.then的部分,都转变为异步。

到此,Q提供的Promise设计原理q0~q7,全部结束。
结语

即便本文已经是这么长的篇幅,但所讲述的也只到基础的Promise。大部分Promise库会有更多的API来应对更多和Promise有关的需求,例如all()、spread(),不过,读到这里,你已经了解了实现Promise的核心理念,这一定对你今后应用Promise有所帮助。

在我看来,Promise是精巧的设计,我花了相当一些时间才差不多理解它。Q作为一个典型Promise库,在思路上走得很明确。可以感受到,再复杂的库也是先从基本的要点开始的,如果我们自己要做类似的事,也应该保持这样的心态一点一点进步。

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