Написал от руки Promise/A+ и отлично прошел официальные 872 тестовых примера.

внешний интерфейс JavaScript

Некоторое время назад я использовал две статьи, чтобы подробно объяснить концепцию асинхронности и лежащий в основе цикла событий, а затем я также рассказал о модели публикации-подписки, которая реализует асинхронность самостоятельно:

Кто будет выполнять Settimeout и Setimmediate первым? Эта статья даст вам тщательное понимание петли событий

Прочитайте и поймите исходный код EventEmitter Node.js из модели публикации-подписки.

В этой статье будет рассказано о другой более современной асинхронной реализации:Promise. Обещание почти обязательно для интервью, поэтому мы можем не только использовать его, но и знать его основополагающие принципы.Лучший способ изучить его принципы — выполнить обещание самим. Итак, в этой статье будет реализовано продолжениеPromise/A+Канонические обещания. После реализации нам также нужно использоватьPromise/A+Для проверки правильности нашей реализации используется официальный тестовый инструмент, который содержит в общей сложности 872 тестовых примера.Promise/A+спецификации, вот ссылка на них:

Promise/A+Технические характеристики:GitHub.com/promises-AP…

Promise/A+инструменты для тестирования:GitHub.com/promises-AP…

Полный код этой статьи размещен на GitHub:GitHub.com/Денис — см....

Обещание использования

В Интернете есть много основных способов использования обещаний. Я кратко упомяну их здесь. Я все еще использую в качестве примера три взаимозависимых сетевых запроса. Если у нас есть три сетевых запроса, запрос 2 должен зависеть от результата запроса 1, а запрос 3 должно зависеть от запроса 2. В результате, если вы используете коллбэки, будет три уровня, и вы попадете в «ад колбэков».Использование промиса намного понятнее:

const request = require("request");

// 我们先用Promise包装下三个网络请求
// 请求成功时resolve这个Promise
const request1 = function() {
  const promise = new Promise((resolve) => {
    request('https://www.baidu.com', function (error, response) {
      if (!error && response.statusCode == 200) {
        resolve('request1 success');
      }
    });
  });

  return promise;
}

const request2 = function() {
  const promise = new Promise((resolve) => {
    request('https://www.baidu.com', function (error, response) {
      if (!error && response.statusCode == 200) {
        resolve('request2 success');
      }
    });
  });

  return promise;
}

const request3 = function() {
  const promise = new Promise((resolve) => {
    request('https://www.baidu.com', function (error, response) {
      if (!error && response.statusCode == 200) {
        resolve('request3 success');
      }
    });
  });

  return promise;
}


// 先发起request1,等他resolve后再发起request2,
// 然后是request3
request1().then((data) => {
  console.log(data);
  return request2();
})
.then((data) => {
  console.log(data);
  return request3();
})
.then((data) => {
  console.log(data);
})

В приведенном выше примереthenЕго можно вызывать по цепочке, последнийthenможет получить фронтresolveЧто касается выводимых данных, мы видим, что в консоли последовательно печатаются три успеха:

image-20200324164123892

Обещания/спецификация A+

В приведенном выше примере мы уже знаем, как выглядит промис, спецификация Promises/A+ на самом деле является дополнительной спецификацией этого внешнего вида. Ниже я немного поясню об этой спецификации.

период, термин

  1. promise: это владениеthenОбъект или функция методов, поведение которых соответствует этой спецификации.

  2. thenable: это определениеthenметод объекта или функции. В основном это используется для обеспечения совместимости с некоторыми старыми реализациями Promise, если реализация Promise доступна, т. е. имеетthenметод, он может быть совместим с Promises/A+.

  3. value:Ссылаться наresloveВыходным значением может быть любое допустимое значение JS (включаяundefined, затемное и обещание и т. д.)

  4. exception: Исключение, используемое в Promisethrowброшенное значение

  5. reason: Причина отказа, даrejectПараметры, переданные внутри, указывают, чтоrejectпричина

Состояние обещания

Обещания имеют в общей сложности три состояния:

  1. pending: обещание находится в этом состоянии до того, как оно будет разрешено или отклонено.
  2. fulfilled: после того, как обещание разрешено, оно находится вfulfilledсостояние, которое больше не может быть изменено и должно иметьнеизменныйзначение (value).
  3. rejected: после того, как обещание отклонено, оно находится вrejectedсостояние, которое больше не может быть изменено и должно иметьнеизменныйпричина отказа(reason).

Обратите внимание, что здесьнеизменныйОтносится===, то есть еслиvalueилиreasonЭто объект, до тех пор, пока ссылка гарантированно остается неизменной, спецификация не предписывает, чтобы свойства в нем оставались неизменными. Состояние обещания на самом деле очень простое, рисование картинки:

image-20200324173555225

затем метод

Обещание должно иметьthenспособ получить доступ к его значению или отклонить причину.thenМетод имеет два параметра:

promise.then(onFulfilled, onRejected)

необязательный параметр

onFulfilledа такжеonRejectedявляются необязательными параметрами.

  • еслиonFulfilledэто не функция, ее нужно игнорировать
  • еслиonRejectedэто не функция, ее нужно игнорировать

onFulfilledхарактеристика

еслиonFulfilledэто функция:

  • когдаpromiseОн должен быть вызван после выполнения, его первый параметрpromiseокончательная стоимостьvalue
  • существуетpromiseЕго нельзя вызвать, пока не завершится выполнение
  • Его нельзя вызывать более одного раза

onRejectedхарактеристика

еслиonRejectedэто функция:

  • когдаpromiseОн должен быть вызван после отклонения выполнения, его первый параметрpromiseпричинаreason
  • существуетpromiseЕго нельзя вызвать, пока выполнение не будет отклонено
  • Его нельзя вызывать более одного раза

несколько вызовов

thenметоды могут быть использованы одним и тем жеpromiseзвонить несколько раз

  • когдаpromiseПри успешном выполнении всеonFulfilledОн должен быть отозван по порядку в соответствии с порядком его регистрации.
  • когдаpromiseПри отказе всеonRejectedОн должен быть отозван по порядку в соответствии с порядком его регистрации.

вернуть

thenметод должен возвращатьpromiseобъект.

promise2 = promise1.then(onFulfilled, onRejected); 
  • еслиonFulfilledилиonRejectedвернуть значениеx, затем запуститеПроцесс разрешения обещаний:[[Resolve]](promise2, x)
  • еслиonFulfilledилиonRejectedбросить исключениеe,ноpromise2Должен отказаться от исполнения и вернуть причину отказаe
  • еслиonFulfilledне является функцией иpromise1успешно выполнен,promise2Должен успешно выполняться и возвращать то же значение
  • еслиonRejectedне является функцией иpromise1отказаться от исполнения,promise2Должен отклонить выполнение и вернуть ту же причину

Большая часть спецификации объясняетПроцесс разрешения обещанийДа, просто смотреть на спецификации очень пусто.Предыдущие спецификации уже могут подтолкнуть нас к написанию собственного промиса.Процесс разрешения обещанийОб этом мы подробно напишем позже.

Напишите обещание самостоятельно

Если мы хотим сами написать промис, нам обязательно нужно знать, какая работа должна быть сделана.Давайте сначала проследим за использованием промиса, чтобы увидеть, что нужно сделать:

  1. Необходимо использовать новое обещаниеnewключевое слово, то он должен вызываться объектно-ориентированным способом, а Promise — это класс.Более подробное объяснение объектной ориентации в JS см. в этой статье.
  2. насnew Promise(fn)Когда вам нужно передать функцию, указав, что параметр Promise является функцией
  3. конструктор передан вfnполучитresolveа такжеrejectДве функции, используемые для индикации успеха и неудачи Promise, указывающие на то, что конструктору также требуетсяresolveа такжеrejectЭти две функции, роль этих двух функций заключается в изменении состояния промиса.
  4. Согласно спецификации промисы имеютpending,fulfilled,rejectedТри состояния, начальное состояниеpending,передачаresolveизменил бы его наfulfilled,передачаrejectбудет изменен наrejected.
  5. После создания объекта экземпляра обещания его можно вызватьthenМетод, и его можно назвать цепнымthenМетод, объяснениеthenявляется методом экземпляра.Реализация цепного вызова подробно объясняется в этой статье, и я не буду повторяться здесь.. Проще говоряthenМетод также должен возвращатьthenОбъект метода, который может быть this или новым экземпляром обещания.

Конструктор

Для лучшей совместимости у нас нет ES6.

// 先定义三个常量表示状态
var PENDING = 'pending';
var FULFILLED = 'fulfilled';
var REJECTED = 'rejected';

function MyPromise(fn) {
  this.status = PENDING;    // 初始状态为pending
  this.value = null;        // 初始化value
  this.reason = null;       // 初始化reason
}

resolveа такжеrejectметод

Согласно спецификации,resolveМетод заключается в изменении статуса на выполнено,rejectзаключается в изменении статуса на отклонено.

// 这两个方法直接写在构造函数里面
function MyPromise(fn) {
  // ...省略前面代码...
  
  // 存一下this,以便resolve和reject里面访问
  var that = this;
  // resolve方法参数是value
  function resolve(value) {
    if(that.status === PENDING) {
      that.status = FULFILLED;
      that.value = value;
    }
  }
  
  // reject方法参数是reason
  function reject(reason) {
    if(that.status === PENDING) {
      that.status = REJECTED;
      that.reason = reason;
    }
  }
}

вызов параметров конструктора

наконецresolveа такжеrejectВызовите входящие параметры как параметры, не забудьте добавитьtry, если обнаружена ошибкаreject.

function MyPromise(fn) {
  // ...省略前面代码...
  
  try {
    fn(resolve, reject);
  } catch (error) {
    reject(error);
  }
}

thenметод

Согласно нашему предыдущему анализу,thenМетоды можно связывать в цепочку, поэтому он экземплярный метод, а API в спецификацииpromise.then(onFulfilled, onRejected), давайте сначала построим полку:

MyPromise.prototype.then = function(onFulfilled, onRejected) {}

ЭтоthenЧто надо сделать в методе, собственно спецификация нам и говорит, сначала проверьтеonFulfilledа такжеonRejectedЯвляется ли это функцией, если это не функция, игнорируйте их.Так называемое «игнорирование» не означает ничегонеделания.onFulfilled«Игнорировать» означаетvalueвернуть в целости и сохранностиonRejectedвозвращатьreason,onRejectedПоскольку это неправильная ветвь, мы возвращаемсяreasonдолжен выдать ошибку:

MyPromise.prototype.then = function(onFulfilled, onRejected) {
  // 如果onFulfilled不是函数,给一个默认函数,返回value
  var realOnFulfilled = onFulfilled;
  if(typeof realOnFulfilled !== 'function') {
    realOnFulfilled = function (value) {
      return value;
    }
  }

  // 如果onRejected不是函数,给一个默认函数,返回reason的Error
  var realOnRejected = onRejected;
  if(typeof realOnRejected !== 'function') {
    realOnRejected = function (reason) {
      throw reason;
    }
  }
}

После того, как параметры проверены, пришло время заняться реальным делом.Подумайте о том, когда мы используем промис, если операция обещания прошла успешно, мы вызовемthenвнутриonFulfilled, если он потерпит неудачу, он вызоветonRejected. В соответствии с нашим кодом мы должны проверить статус промиса, если онFULFILLED, просто позвониonFulfilled,еслиREJECTED, просто позвониonRejected:

MyPromise.prototype.then = function(onFulfilled, onRejected) {
  // ...省略前面代码...

  if(this.status === FULFILLED) {
    onFulfilled(this.value)
  }

  if(this.status === REJECTED) {
    onRejected(this.reason);
  }
}

Подумайте еще раз, когда мы создаем новое обещание, мы можем использовать его прямо так:

new Promise(fn).then(onFulfilled, onRejected);

код вышеthenОн вызывается сразу после создания экземпляра объекта.fnВнутри асинхронная операция может быть еще не закончена, что егоstatusещеPENDING, что нам делать?В это время мы не должны быть в состоянии приспособиться немедленно.onFulfilledилиonRejected, потому чтоfnУдалось это или нет, неизвестно.А когда будешь знатьfnУспех или неудача? ответfnАктивно настроен внутриresolveилиrejectкогда. Так что, если на этот разstatusстатус все ещеPENDING, нам следуетonFulfilledа такжеonRejectedДва обратных вызова сохраняются и ждут, покаfnС выводом,resolveилиrejectЗатем снова вызовите соответствующий код. потому что позадиthenЕсть также цепные вызовы, их будет несколькоonFulfilledа такжеonRejected, я использую здесь два массива для их хранения и т. д.resolveилиrejectКогда все методы в массиве вынимаются и выполняются снова:

// 构造函数
function MyPromise(fn) {
  // ...省略其他代码...
  
  // 构造函数里面添加两个数组存储成功和失败的回调
  this.onFulfilledCallbacks = [];
  this.onRejectedCallbacks = [];
  
  function resolve(value) {
    if(that.status === PENDING) {
      // ...省略其他代码...
      // resolve里面将所有成功的回调拿出来执行
      that.onFulfilledCallbacks.forEach(callback => {
        callback(that.value);
      });
    }
  }
  
  function reject(reason) {
    if(that.status === PENDING) {
      // ...省略其他代码...
      // resolve里面将所有失败的回调拿出来执行
      that.onRejectedCallbacks.forEach(callback => {
        callback(that.reason);
      });
    }
  }
}

// then方法
MyPromise.prototype.then = function(onFulfilled, onRejected) {
  // ...省略其他代码...

  // 如果还是PENDING状态,将回调保存下来
  if(this.status === PENDING) {
    this.onFulfilledCallbacks.push(realOnFulfilled);
    this.onRejectedCallbacks.push(realOnRejected);
  }
}

Приведенный выше метод временного сохранения обратного вызова и его запуска при выполнении условий напоминает мне модель: модель подписки-публикации. Заходим в массив обратного вызоваpushФункция обратного вызова фактически эквивалентна регистрации события в центре событий.resolveЭто эквивалентно публикации успешного события, всех зарегистрированных событий, а именноonFulfilledCallbacksВсе методы в нем будут вынесены и выполнены, и так же верноrejectЭто эквивалентно размещению события неудачи.Дополнительные принципы модели подписки-публикации можно найти здесь..

сделал небольшой шаг

На данный момент мы можем реализовать асинхронные вызовы, простоthenВозвращаемое значение не было реализовано, а цепочка вызовов еще не может быть реализована, давайте сначала поиграем с этим:

var request = require("request");
var MyPromise = require('./MyPromise');

var promise1 = new MyPromise((resolve) => {
  request('https://www.baidu.com', function (error, response) {
    if (!error && response.statusCode == 200) {
      resolve('request1 success');
    }
  });
});

promise1.then(function(value) {
  console.log(value);
});

var promise2 = new MyPromise((resolve, reject) => {
  request('https://www.baidu.com', function (error, response) {
    if (!error && response.statusCode == 200) {
      reject('request2 failed');
    }
  });
});

promise2.then(function(value) {
  console.log(value);
}, function(reason) {
  console.log(reason);
});

Вывод приведенного выше кода выглядит следующим образом, что соответствует нашим ожиданиям, указывая на то, что пока с нашим кодом все в порядке:

image-20200325172257655

thenВозвращаемое значение

Согласно спецификацииthenВозвращаемое значение должно быть обещанием. Спецификация также определяет, как должны обрабатываться различные ситуации. Давайте сначала разберемся с несколькими более простыми ситуациями:

  1. еслиonFulfilledилиonRejectedбросить исключениеe,ноpromise2Должен отказаться от исполнения и вернуть причину отказаe.
MyPromise.prototype.then = function(onFulfilled, onRejected) {
	// ... 省略其他代码 ...
  
  // 有了这个要求,在RESOLVED和REJECTED的时候就不能简单的运行onFulfilled和onRejected了。
  // 我们需要将他们用try...catch...包起来,如果有错就reject。
  if(this.status === FULFILLED) {
    var promise2 = new MyPromise(function(resolve, reject) {
      try {
        realOnFulfilled(that.value);
      } catch (error) {
        reject(error);
      }
    });
  
    return promise2;
  }

  if(this.status === REJECTED) {
    var promise2 = new MyPromise(function(resolve, reject) {
      try {
        realOnRejected(that.reason);
      } catch (error) {
        reject(error);
      }
    });
  
    return promise2;
  }
  
  // 如果还是PENDING状态,也不能直接保存回调方法了,需要包一层来捕获错误
  if(this.status === PENDING) {
    var promise2 = new MyPromise(function(resolve, reject) {
      that.onFulfilledCallbacks.push(function() {
        try {
          realOnFulfilled(that.value);
        } catch (error) {
          reject(error);
        }
      });
      that.onRejectedCallbacks.push(function() {
        try {
          realOnRejected(that.reason);
        } catch (error) {
          reject(error);
        }
      });
    });
  
    return promise2;
  }
}
  1. еслиonFulfilledне является функцией иpromise1успешно выполнен,promise2Должен успешно выполняться и возвращать то же значение
// 我们就根据要求加个判断,注意else里面是正常执行流程,需要resolve
// 这是个例子,每个realOnFulfilled后面都要这样写
  if(this.status === FULFILLED) {
    var promise2 = new MyPromise(function(resolve, reject) {
      try {
        if (typeof onFulfilled !== 'function') {
          resolve(that.value);
        } else {
          realOnFulfilled(that.value);
          resolve(that.value);
        }
      } catch (error) {
        reject(error);
      }
    });
  
    return promise2;
  }
  1. еслиonRejectedне является функцией иpromise1отказаться от исполнения,promise2В исполнении должно быть отказано и возвращена та же причина. Это требование на самом деле в нашем обнаруженииonRejectedЭто было сделано, когда это не функция, потому что мы даем это по умолчаниюonRejectedВнутри будет выброшен Error, так что код точно попадет в ловушку. Но для большей интуитивности код все же соответствует спецификации один за другим. Следует отметить, что еслиpromise1изonRejectedКазнь прошла успешно,promise2должно бытьresolve. Код модификации выглядит следующим образом:
  if(this.status === REJECTED) {
    var promise2 = new MyPromise(function(resolve, reject) {
      try {
        if(typeof onRejected !== 'function') {
          reject(that.reason);
        } else {
          realOnRejected(that.reason);
          resolve();
        }
      } catch (error) {
        reject(error);
      }
    });
  
    return promise2;
  }
  1. еслиonFulfilledилиonRejectedвернуть значениеx, затем выполните следующееПроцесс разрешения обещаний:[[Resolve]](promise2, x). Эта статья на самом деле является первой статьей спецификации, потому что она более хлопотная, поэтому я поместил ее в конец. Реализация нашего кода ранее, по сути, должна была толькоonRejectedилиonFulfilledуспешно выполнено, мы всеresolve promise2. В дополнение к этому нам также необходимоonRejectedилиonFulfilledВозвращаемое значение оценивается, и если есть возвращаемое значение,Процесс разрешения обещаний. Мы специально пишем метод для выполненияПроцесс разрешения обещаний. Реализация нашего кода ранее, по сути, должна была толькоonRejectedилиonFulfilledуспешно выполнено, мы всеresolve promise2, давайте поместим этот процесс в этот метод, чтобы код стал таким, а другие места аналогичны:
  if(this.status === FULFILLED) {
    var promise2 = new MyPromise(function(resolve, reject) {
      try {
        if (typeof onFulfilled !== 'function') {
          resolve(that.value);
        } else {
          var x = realOnFulfilled(that.value);
          resolvePromise(promise2, x, resolve, reject);   // 调用Promise 解决过程
        }
      } catch (error) {
        reject(error);
      }
    });
  
    return promise2;
  }

Процесс разрешения обещаний

Теперь мы должны реализоватьresolvePromiseметод, эта часть спецификации длиннее, поэтому я прямо пишу спецификацию в виде комментария в коде.

function resolvePromise(promise, x, resolve, reject) {
  // 如果 promise 和 x 指向同一对象,以 TypeError 为据因拒绝执行 promise
  // 这是为了防止死循环
  if (promise === x) {
    return reject(new TypeError('The promise and the return value are the same'));
  }

  if (x instanceof MyPromise) {
    // 如果 x 为 Promise ,则使 promise 接受 x 的状态
    // 也就是继续执行x,如果执行的时候拿到一个y,还要继续解析y
    // 这个if跟下面判断then然后拿到执行其实重复了,可有可无
    x.then(function (y) {
      resolvePromise(promise, y, resolve, reject);
    }, reject);
  }
  // 如果 x 为对象或者函数
  else if (typeof x === 'object' || typeof x === 'function') {
    // 这个坑是跑测试的时候发现的,如果x是null,应该直接resolve
    if (x === null) {
      return resolve(x);
    }

    try {
      // 把 x.then 赋值给 then 
      var then = x.then;
    } catch (error) {
      // 如果取 x.then 的值时抛出错误 e ,则以 e 为据因拒绝 promise
      return reject(error);
    }

    // 如果 then 是函数
    if (typeof then === 'function') {
      var called = false;
      // 将 x 作为函数的作用域 this 调用之
      // 传递两个回调函数作为参数,第一个参数叫做 resolvePromise ,第二个参数叫做 rejectPromise
      // 名字重名了,我直接用匿名函数了
      try {
        then.call(
          x,
          // 如果 resolvePromise 以值 y 为参数被调用,则运行 [[Resolve]](promise, y)
          function (y) {
            // 如果 resolvePromise 和 rejectPromise 均被调用,
            // 或者被同一参数调用了多次,则优先采用首次调用并忽略剩下的调用
            // 实现这条需要前面加一个变量called
            if (called) return;
            called = true;
            resolvePromise(promise, y, resolve, reject);
          },
          // 如果 rejectPromise 以据因 r 为参数被调用,则以据因 r 拒绝 promise
          function (r) {
            if (called) return;
            called = true;
            reject(r);
          });
      } catch (error) {
        // 如果调用 then 方法抛出了异常 e:
        // 如果 resolvePromise 或 rejectPromise 已经被调用,则忽略之
        if (called) return;

        // 否则以 e 为据因拒绝 promise
        reject(error);
      }
    } else {
      // 如果 then 不是函数,以 x 为参数执行 promise
      resolve(x);
    }
  } else {
    // 如果 x 不为对象或者函数,以 x 为参数执行 promise
    resolve(x);
  }
}

onFulfilledа такжеonRejectedвремя исполнения

В спецификации также есть строчка:onFulfilledа такжеonRejectedТолько если стек среды исполнения содержит толькокод платформыможно только позвонить. Это означает, что на практике необходимо обеспечитьonFulfilledа такжеonRejectedметод выполняется асинхронно и должен бытьthenВыполняется в новом стеке выполнения после цикла обработки событий, в котором был вызван метод. Итак, когда мы выполняемonFulfilledа такжеonRejectedследует включать, когдаsetTimeoutЗайти внутрь.

// 这块代码在then里面
if(this.status === FULFILLED) {
  var promise2 = new MyPromise(function(resolve, reject) {
    // 这里加setTimeout
    setTimeout(function() {
      try {
        if (typeof onFulfilled !== 'function') {
          resolve(that.value);
        } else {
          var x = realOnFulfilled(that.value);
          resolvePromise(promise2, x, resolve, reject);
        }
      } catch (error) {
        reject(error);
      }
    }, 0);
  });

  return promise2;
}

if(this.status === REJECTED) {
  var promise2 = new MyPromise(function(resolve, reject) {
    // 这里加setTimeout
    setTimeout(function() {
      try {
        if(typeof onRejected !== 'function') {
          reject(that.reason);
        } else {
          var x = realOnRejected(that.reason);
          resolvePromise(promise2, x, resolve, reject);
        }
      } catch (error) {
        reject(error);
      }
    }, 0);
  });

  return promise2;
}

if (this.status === PENDING) {
  var promise2 = new MyPromise(function (resolve, reject) {
    that.onFulfilledCallbacks.push(function () {
      // 这里加setTimeout
      setTimeout(function () {
        try {
          if (typeof onFulfilled !== 'function') {
            resolve(that.value);
          } else {
            var x = realOnFulfilled(that.value);
            resolvePromise(promise2, x, resolve, reject);
          }
        } catch (error) {
          reject(error);
        }
      }, 0);
    });
    that.onRejectedCallbacks.push(function () {
      // 这里加setTimeout
      setTimeout(function () {
        try {
          if (typeof onRejected !== 'function') {
            reject(that.reason);
          } else {
            var x = realOnRejected(that.reason);
            resolvePromise(promise2, x, resolve, reject);
          }
        } catch (error) {
          reject(error);
        }
      }, 0)
    });
  });

  return promise2;
}

Проверьте наши обещания

Мы используем официальные инструменты тестирования Promise/A+promises-aplus-testsприходите к намMyPromiseЧтобы протестировать, чтобы использовать этот инструмент, мы должны реализовать статический методdeferred, официальное определение этого метода выглядит следующим образом:

deferred: возвращает объект, содержащий { обещание, разрешение, отклонение }

promiseэтоpendingгосударственные обещания

resolve(value)использоватьvalueрешить вышеуказанноеpromise

reject(reason)использоватьreasonотвергнуть тот, что вышеpromise

Мы реализуем код следующим образом:

MyPromise.deferred = function() {
  var result = {};
  result.promise = new MyPromise(function(resolve, reject){
    result.resolve = resolve;
    result.reject = reject;
  });

  return result;
}

Затем используйте npm дляpromises-aplus-testsЗагрузите его, а затем настройте package.json для запуска теста:

{
  "devDependencies": {
    "promises-aplus-tests": "^2.1.2"
  },
  "scripts": {
    "test": "promises-aplus-tests MyPromise"
  }
}

Я обнаружил яму при запуске теста, вresolvePromise, если х естьnull, его тип такжеobject, вы должны использовать x для прямого разрешения, предыдущий код перейдет кcatchпотомreject, значит надо проверитьnull:

// 这个坑是跑测试的时候发现的,如果x是null,应该直接resolve
if(x === null) {
  return resolve(x);
}

Этот тест имеет в общей сложности 872 варианта использования, и обещание, которое мы написали, полностью прошло их все:

image-20200326214543894

Другие методы обещаний

В официальном обещании ES6 есть много API, например:

Promise.resolve

Promise.reject

Promise.all

Promise.race

Promise.prototype.catch

Promise.prototype.finally

Promise.allSettled

Хотя их нет в Promise/A+, давайте реализуем их и углубим наше понимание. На самом деле, реализовать Promise/A+ раньше для нас проще простого, потому что все эти API — это предыдущая инкапсуляция.

Promise.resolve

Преобразуйте существующий объект в объект Promise, если параметр метода Promise.resolve не является объектом с методом then (также известным как объект thenable), тогда возвращается новый объект Promise, и его состояние выполняется.

MyPromise.resolve = function(parameter) {
  if(parameter instanceof MyPromise) {
    return parameter;
  }

  return new MyPromise(function(resolve) {
    resolve(parameter);
  });
}

Promise.reject

Возвращает новый экземпляр Promise с отклоненным состоянием. Причина параметра метода Promise.reject будет передана функции обратного вызова экземпляра.

MyPromise.reject = function(reason) {
  return new MyPromise(function(resolve, reject) {
    reject(reason);
  });
}

Promise.all

Этот метод используется для переноса нескольких экземпляров Promise в новый экземпляр Promise.

const p = Promise.all([p1, p2, p3]);

Promise.all()Метод принимает массив в качестве параметра,p1,p2,p3Оба являются экземплярами Promise, в противном случае они будут вызваны первыми.Promise.resolveметод, который преобразует параметр в экземпляр Promise для дальнейшей обработки. Когда все p1, p2 и p3 разрешены, большое обещание разрешается.Если какой-либо из них отклонен, большое обещание отклоняется.

MyPromise.all = function(promiseList) {
  var resPromise = new MyPromise(function(resolve, reject) {
    var count = 0;
    var result = [];
    var length = promiseList.length;

    if(length === 0) {
      return resolve(result);
    }

    promiseList.forEach(function(promise, index) {
      MyPromise.resolve(promise).then(function(value){
        count++;
        result[index] = value;
        if(count === length) {
          resolve(result);
        }
      }, function(reason){
        reject(reason);
      });
    });
  });

  return resPromise;
}

Promise.race

Применение:

const p = Promise.race([p1, p2, p3]);

Этот метод также оборачивает несколько экземпляров Promise в новый экземпляр Promise. В приведенном выше коде, покаp1,p2,p3Один из экземпляров первым меняет состояние,pстатус меняется соответственно. Возвращаемое значение экземпляра Promise, который изменился первым, передается вpфункция обратного вызова.

MyPromise.race = function(promiseList) {
  var resPromise = new MyPromise(function(resolve, reject) {
    var length = promiseList.length;

    if(length === 0) {
      return resolve();
    } else {
      for(var i = 0; i < length; i++) {
        MyPromise.resolve(promiseList[i]).then(function(value) {
          return resolve(value);
        }, function(reason) {
          return reject(reason);
        });
      }
    }
  });

  return resPromise;
}

Promise.prototype.catch

Promise.prototype.catchпуть.then(null, rejection)или.then(undefined, rejection)Псевдоним для указания функции обратного вызова при возникновении ошибки.

MyPromise.prototype.catch = function(onRejected) {
  this.then(null, onRejected);
}

Promise.prototype.finally

finallyМетоды используются для указания действий, которые будут выполняться независимо от конечного состояния объекта Promise. Этот метод является стандартным, представленным в ES2018.

MyPromise.prototype.finally = function (fn) {
  return this.then(function (value) {
    return MyPromise.resolve(fn()).then(function () {
      return value;
    });
  }, function (error) {
    return MyPromise.resolve(fn()).then(function () {
      throw error
    });
  });
}

Promise.allSettled

Метод принимает в качестве параметра заданный пример Promise, упакованный в новый экземпляр Promise. Только подождите, пока все эти экземпляры параметров вернут результат, будь тоfulfilledещеrejected, экземпляр оболочки завершится. Метод состоит изES2020Представлять. Новый экземпляр Promise, возвращаемый этим методом, после завершения всегда находится в состоянииfulfilled, не станетrejected. состояние становитсяfulfilledПосле этого параметр, полученный функцией слушателя Promise, представляет собой массив, каждый член которого соответствует входящемуPromise.allSettled()Результат выполнения экземпляра Promise.

MyPromise.allSettled = function(promiseList) {
  return new MyPromise(function(resolve){
    var length = promiseList.length;
    var result = [];
    var count = 0;

    if(length === 0) {
      return resolve(result);
    } else {
      for(var i = 0; i < length; i++) {

        (function(i){
          var currentPromise = MyPromise.resolve(promiseList[i]);

          currentPromise.then(function(value){
            count++;
            result[i] = {
              status: 'fulfilled',
              value: value
            }
            if(count === length) {
              return resolve(result);
            }
          }, function(reason){
            count++;
            result[i] = {
              status: 'rejected',
              reason: reason
            }
            if(count === length) {
              return resolve(result);
            }
          });
        })(i)
      }
    }
  });
}

полный код

Полная версия кода длиннее, если вам здесь не ясно, вы можете перейти на мой GitHub, чтобы увидеть:

GitHub.com/Денис — см....

// 先定义三个常量表示状态
var PENDING = 'pending';
var FULFILLED = 'fulfilled';
var REJECTED = 'rejected';

function MyPromise(fn) {
  this.status = PENDING;    // 初始状态为pending
  this.value = null;        // 初始化value
  this.reason = null;       // 初始化reason

  // 构造函数里面添加两个数组存储成功和失败的回调
  this.onFulfilledCallbacks = [];
  this.onRejectedCallbacks = [];

  // 存一下this,以便resolve和reject里面访问
  var that = this;
  // resolve方法参数是value
  function resolve(value) {
    if (that.status === PENDING) {
      that.status = FULFILLED;
      that.value = value;

      // resolve里面将所有成功的回调拿出来执行
      that.onFulfilledCallbacks.forEach(callback => {
        callback(that.value);
      });
    }
  }

  // reject方法参数是reason
  function reject(reason) {
    if (that.status === PENDING) {
      that.status = REJECTED;
      that.reason = reason;

      // resolve里面将所有失败的回调拿出来执行
      that.onRejectedCallbacks.forEach(callback => {
        callback(that.reason);
      });
    }
  }

  try {
    fn(resolve, reject);
  } catch (error) {
    reject(error);
  }
}

function resolvePromise(promise, x, resolve, reject) {
  // 如果 promise 和 x 指向同一对象,以 TypeError 为据因拒绝执行 promise
  // 这是为了防止死循环
  if (promise === x) {
    return reject(new TypeError('The promise and the return value are the same'));
  }

  if (x instanceof MyPromise) {
    // 如果 x 为 Promise ,则使 promise 接受 x 的状态
    // 也就是继续执行x,如果执行的时候拿到一个y,还要继续解析y
    // 这个if跟下面判断then然后拿到执行其实重复了,可有可无
    x.then(function (y) {
      resolvePromise(promise, y, resolve, reject);
    }, reject);
  }
  // 如果 x 为对象或者函数
  else if (typeof x === 'object' || typeof x === 'function') {
    // 这个坑是跑测试的时候发现的,如果x是null,应该直接resolve
    if (x === null) {
      return resolve(x);
    }

    try {
      // 把 x.then 赋值给 then 
      var then = x.then;
    } catch (error) {
      // 如果取 x.then 的值时抛出错误 e ,则以 e 为据因拒绝 promise
      return reject(error);
    }

    // 如果 then 是函数
    if (typeof then === 'function') {
      var called = false;
      // 将 x 作为函数的作用域 this 调用之
      // 传递两个回调函数作为参数,第一个参数叫做 resolvePromise ,第二个参数叫做 rejectPromise
      // 名字重名了,我直接用匿名函数了
      try {
        then.call(
          x,
          // 如果 resolvePromise 以值 y 为参数被调用,则运行 [[Resolve]](promise, y)
          function (y) {
            // 如果 resolvePromise 和 rejectPromise 均被调用,
            // 或者被同一参数调用了多次,则优先采用首次调用并忽略剩下的调用
            // 实现这条需要前面加一个变量called
            if (called) return;
            called = true;
            resolvePromise(promise, y, resolve, reject);
          },
          // 如果 rejectPromise 以据因 r 为参数被调用,则以据因 r 拒绝 promise
          function (r) {
            if (called) return;
            called = true;
            reject(r);
          });
      } catch (error) {
        // 如果调用 then 方法抛出了异常 e:
        // 如果 resolvePromise 或 rejectPromise 已经被调用,则忽略之
        if (called) return;

        // 否则以 e 为据因拒绝 promise
        reject(error);
      }
    } else {
      // 如果 then 不是函数,以 x 为参数执行 promise
      resolve(x);
    }
  } else {
    // 如果 x 不为对象或者函数,以 x 为参数执行 promise
    resolve(x);
  }
}

MyPromise.prototype.then = function (onFulfilled, onRejected) {
  // 如果onFulfilled不是函数,给一个默认函数,返回value
  // 后面返回新promise的时候也做了onFulfilled的参数检查,这里可以删除,暂时保留是为了跟规范一一对应,看得更直观
  var realOnFulfilled = onFulfilled;
  if (typeof realOnFulfilled !== 'function') {
    realOnFulfilled = function (value) {
      return value;
    }
  }

  // 如果onRejected不是函数,给一个默认函数,返回reason的Error
  // 后面返回新promise的时候也做了onRejected的参数检查,这里可以删除,暂时保留是为了跟规范一一对应,看得更直观
  var realOnRejected = onRejected;
  if (typeof realOnRejected !== 'function') {
    realOnRejected = function (reason) {
      throw reason;
    }
  }

  var that = this;   // 保存一下this

  if (this.status === FULFILLED) {
    var promise2 = new MyPromise(function (resolve, reject) {
      setTimeout(function () {
        try {
          if (typeof onFulfilled !== 'function') {
            resolve(that.value);
          } else {
            var x = realOnFulfilled(that.value);
            resolvePromise(promise2, x, resolve, reject);
          }
        } catch (error) {
          reject(error);
        }
      }, 0);
    });

    return promise2;
  }

  if (this.status === REJECTED) {
    var promise2 = new MyPromise(function (resolve, reject) {
      setTimeout(function () {
        try {
          if (typeof onRejected !== 'function') {
            reject(that.reason);
          } else {
            var x = realOnRejected(that.reason);
            resolvePromise(promise2, x, resolve, reject);
          }
        } catch (error) {
          reject(error);
        }
      }, 0);
    });

    return promise2;
  }

  // 如果还是PENDING状态,将回调保存下来
  if (this.status === PENDING) {
    var promise2 = new MyPromise(function (resolve, reject) {
      that.onFulfilledCallbacks.push(function () {
        setTimeout(function () {
          try {
            if (typeof onFulfilled !== 'function') {
              resolve(that.value);
            } else {
              var x = realOnFulfilled(that.value);
              resolvePromise(promise2, x, resolve, reject);
            }
          } catch (error) {
            reject(error);
          }
        }, 0);
      });
      that.onRejectedCallbacks.push(function () {
        setTimeout(function () {
          try {
            if (typeof onRejected !== 'function') {
              reject(that.reason);
            } else {
              var x = realOnRejected(that.reason);
              resolvePromise(promise2, x, resolve, reject);
            }
          } catch (error) {
            reject(error);
          }
        }, 0)
      });
    });

    return promise2;
  }
}

MyPromise.deferred = function () {
  var result = {};
  result.promise = new MyPromise(function (resolve, reject) {
    result.resolve = resolve;
    result.reject = reject;
  });

  return result;
}

MyPromise.resolve = function (parameter) {
  if (parameter instanceof MyPromise) {
    return parameter;
  }

  return new MyPromise(function (resolve) {
    resolve(parameter);
  });
}

MyPromise.reject = function (reason) {
  return new MyPromise(function (resolve, reject) {
    reject(reason);
  });
}

MyPromise.all = function (promiseList) {
  var resPromise = new MyPromise(function (resolve, reject) {
    var count = 0;
    var result = [];
    var length = promiseList.length;

    if (length === 0) {
      return resolve(result);
    }

    promiseList.forEach(function (promise, index) {
      MyPromise.resolve(promise).then(function (value) {
        count++;
        result[index] = value;
        if (count === length) {
          resolve(result);
        }
      }, function (reason) {
        reject(reason);
      });
    });
  });

  return resPromise;
}

MyPromise.race = function (promiseList) {
  var resPromise = new MyPromise(function (resolve, reject) {
    var length = promiseList.length;

    if (length === 0) {
      return resolve();
    } else {
      for (var i = 0; i < length; i++) {
        MyPromise.resolve(promiseList[i]).then(function (value) {
          return resolve(value);
        }, function (reason) {
          return reject(reason);
        });
      }
    }
  });

  return resPromise;
}

MyPromise.prototype.catch = function (onRejected) {
  this.then(null, onRejected);
}

MyPromise.prototype.finally = function (fn) {
  return this.then(function (value) {
    return MyPromise.resolve(fn()).then(function () {
      return value;
    });
  }, function (error) {
    return MyPromise.resolve(fn()).then(function () {
      throw error
    });
  });
}

MyPromise.allSettled = function (promiseList) {
  return new MyPromise(function (resolve) {
    var length = promiseList.length;
    var result = [];
    var count = 0;

    if (length === 0) {
      return resolve(result);
    } else {
      for (var i = 0; i < length; i++) {

        (function (i) {
          var currentPromise = MyPromise.resolve(promiseList[i]);

          currentPromise.then(function (value) {
            count++;
            result[i] = {
              status: 'fulfilled',
              value: value
            }
            if (count === length) {
              return resolve(result);
            }
          }, function (reason) {
            count++;
            result[i] = {
              status: 'rejected',
              reason: reason
            }
            if (count === length) {
              return resolve(result);
            }
          });
        })(i)
      }
    }
  });
}

module.exports = MyPromise;

Суммировать

До сих пор наш Promise был просто реализован, но мы не являемся нативным кодом и не можем создавать микрозадачи.Если мы должны создавать микрозадачи, мы можем только симулировать их с помощью других API микрозадач, таких какMutaionObserverилиprocess.nextTick. Вот несколько ключевых моментов для обзора:

  1. Обещание на самом деле модель подписки, выпущенная
  2. thenметод для ещеpendingЗадача собственно в том, чтобы вызвать callback-функциюonFilfilledа такжеonRejectedзабиты в два массива
  3. Внутри конструктора промисовresolveметод преобразует массивonFilfilledCallbacksВсе методы в нем вынуты и выполнены.Вот успешный callback, который был вставлен в метод then раньше.
  4. Точно так же в конструкторе Promiserejectметод преобразует массивonRejectedCallbacksВсе методы в нем вынуты и выполнены.Вот обратный вызов с ошибкой, который был вставлен в метод then раньше.
  5. thenМетод возвращает новое обещание для выполнения цепочки
  6. catchа такжеfinallyКаждый из этих методов экземпляра должен возвращать новый экземпляр Promise, чтобы связать вызовы.

В конце статьи спасибо, что потратили свое драгоценное время на чтение этой статьи. Если эта статья немного поможет вам или вдохновит, пожалуйста, не скупитесь на лайки и звезды GitHub. Ваша поддержка является движущей силой для автор продолжать творить.

Добро пожаловать, чтобы обратить внимание на мой общедоступный номербольшой фронт атакиПолучите высококачественные оригиналы впервые~

Цикл статей "Передовые передовые знания":nuggets.capable/post/684490…

Адрес GitHub с исходным кодом из серии статей «Advanced Front-end Knowledge»:GitHub.com/Денис — см....