TS-версия Promise в деталях

TypeScript Promise
TS-версия Promise в деталях

Поскольку автор переходит на TypeScript, TypeScript по-прежнему будет использоваться для этой разработки.

Это должен быть последний раз, когда я делюсь статьей с заголовком TypeScript, до свидания 🤞, я могу смело отправляться в путь. (Я столько раз кричал, садись в автобус быстро, но в автобус влезло мало людей, поэтому я выйду первым.)

Эта статья предназначена для нулевого понимания читателем повторного изучения Promise, а знания можно получить следующим образом:

  • Promise важныйТочка знаний

  • Метод реализации Promise API

  • Как передняя часть чувствует себя в безопасности?

Автор надеется, что читатели смогуттолько эта статьяВы можете иметь глубокое понимание промисов,И вы можете сами реализовать класс Promise. Поэтому я буду рассказывать о Promise со всех сторон, а содержания может быть и больше.Рекомендуется читателям на выбор.

Зачем внедрять обещания

Promise появился в Es6, если Es5 нужно использовать Promise, то его обычно нужно использоватьPromise-polyfill. То есть то, что мы хотим реализовать, — это полифилл. Его реализация не только помогает нам получить представление о промисах, но и снижает вероятность ошибок при использовании, что приводит к лучшим практикам промисов.

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

Promise

Промис представляет собой конечный результат асинхронной операции, и основным способом взаимодействия с ним является метод then, который регистрирует две callback-функции для получения конечного значения промиса или причины, по которой промис не может быть выполнен.

Давайте взглянем на схему структуры API, нарисованную автором (если вам не ясно, вы можете ввести моюGitHubСмотри, там большие картинки и исходники xmind ):

Картинка выше простоСхема API для промисов,фактическиPromises/A+Спецификация не определяет, как создаются, разрешаются и отклоняются промисы, а вместо этого фокусируется на предоставлении общего метода then. Таким образом, реализации спецификации Promise/A+ могут прекрасно сосуществовать с менее формальными, но пригодными для использования реализациями. Если все будут следовать норме, проблем с совместимостью будет не так много. (PS: включая веб-стандарты) Давайте поговоримPromises/A+, можете пропустить, если видели.

Promises/A+

Все реализации Promise неотделимы отPromises/A+Технические характеристики, содержания не много, предлагаю пройти. Вот несколько важных моментов в спецификации

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

  • Promiseнадоthenобъект или функция методов, поведение которых соответствуетPromises/A+Технические характеристики;

  • thenableодин определяетthenОбъект или функция метода также можно рассматривать как «принадлежащийthenметод"

  • 值(value)Ссылаться наЛюбыеJavaScriptюридическая ценность(включая undefined , thenable и promise)

  • 异常(exception)использоватьthrowзначение, выброшенное оператором

  • 据因(reason)Указывает причину отклонения обещания.

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

текущее состояние промисадолженЭто одно из следующих трех состояний: Ожидание, Выполнено и Отклонено.

  • 等待态(Pending)

    В состоянии ожидания обещание должно удовлетворять:可以Переход в состояние выполнения или отклонения

  • 执行态(Fulfilled)

    В состоянии выполнения обещание должно удовлетворять:不能Для миграции в любой другой штат необходимо иметь不可变из终值

  • 拒绝态(Rejected)

    В состоянии отклонения обещание должно удовлетворять:不能Для миграции в любой другой штат необходимо иметь不可变из据因

Неизменяемый здесь относится к идентичности (т.е.===равенства), а не подразумевает более глубокую неизменность (имеется в виду, когда ценность или причина небазовая стоимость, должны совпадать только его ссылочные адреса, но значение свойства может быть изменено).

Затем метод

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

Метод then обещания принимает два параметра:

promise.then(onFulfilled, onRejected);
  • onFulfilledа такжеonRejectedявляются необязательными параметрами.

  • еслиonFulfilledэто функция, когда обещаниевыполнение заканчиваетсяпосле чего он должен быть вызван, которыйпервый параметрза обещаниеконечное значение, прежде чем обещание завершит выполнениене подлежит отзыву, который нельзя вызывать более одного раза

  • еслиonRejectedэто функция, когда обещание вызываетсяотклонятьОн должен вызываться после выполнения, егопервый параметрза обещаниеСогласно с, прежде чем обещание будет отклоненоне подлежит отзыву, который нельзя вызывать более одного раза

  • onFulfilledа такжеonRejectedОн доступен для вызова только в том случае, если стек среды выполнения содержит только код платформы (ссылка на движок, среду и обещание реализации кода)

  • На практике обеспечитьonFulfilledа такжеonRejectedметод выполняется асинхронно и должен бытьthenВыполняется в новом стеке выполнения после цикла обработки событий, в котором был вызван метод.

  • onFulfilledа такжеonRejectedДолжна вызываться как функция без этого значения (то есть в строгом режиме функция this не определена, в нестрогом режиме это глобальный объект).

  • Тогда метод можно назвать несколько раз по тому же обещанию

  • тогда метод должен возвращать обещанный объект

Затем параметр (функция) возвращает значение

Я надеюсь, что читатели смогут внимательно прочитать эту часть, чтобы понять обещаниеthenметод очень помогает.

Давайте посмотрим на процесс выполнения обещания:

Общий процесс заключается в том, что обещание начнется сpendingПеревести вfulfilledилиrejected, а затем вызовите соответственноthenпараметр методаonFulfilledилиonRejected, который в итоге возвращаетpromiseобъект.

Для дальнейшего понимания предположим, что есть следующие два обещания:

promise2 = promise1.then(onFulfilled, onRejected);

Будут следующие ситуации:

  1. еслиonFulfilledилиonRejected генерировать исключение e, то обещание2должен отказаться от исполнения, и вернуться拒因 e

  2. еслиonFulfilled не функцияи обещание1 выполняется успешно, обещание2 должно выполниться успешно и вернутьто же значение

  3. еслиonRejected не функцияИ обещание1 отказывается выполняться, обещание2 должно отказаться от выполнения и вернутьсята же причина

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

// 通过改变 isResolve 来切换 promise1 的状态
const isResolve = true;

const promise1 = new Promise((resolve, reject) => {
  if (isResolve) {
    resolve('promise1 执行态');
  } else {
    reject('promise1 拒绝态');
  }
});

// 一、promise1 处于 resolve 以及 onFulfilled 抛出异常 的情况
// promise2 必须拒绝执行,并返回拒因
promise1
  .then(() => {
    throw '抛出异常!';
  })
  .then(
    value => {
      console.log(value);
    },
    reason => {
      console.log(reason);
    }
  );

// 二、promise1 处于 resolve 以及 onFulfilled 不是函数的情况
// promise2 必须成功执行并返回相同的值
promise1.then().then(value => {
  console.log(value);
});

// 三、promise1 处于 reject 以及 onRejected 不是函数的情况
// promise2 必须拒绝执行并返回拒因
promise1.then().then(
  () => {},
  reason => {
    console.log(reason);
  }
);

// 四、promise1 处于 resolve 以及 onFulfilled 有返回值时
promise1
  .then(value => {
    return value;
  })
  .then(value => {
    console.log(value);
  });

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

  1. onFulfilledилиonRejectedвернутьдопустимое значение JavaScriptСлучай

Давайте сначала предположим, что внутри метода then есть метод с именем[[Resolve]]Этот метод используется для работы с этим особым случаем, и ниже приводится подробное описание этого метода.

[[Resolve]]метод

вообще нравится[[...]]Такое представление реализуется внутренне, например[[Resolve]], который принимает два параметра:

[[Resolve]](promise, x);

дляxзначение в следующих случаях:

  • xимеютзатем методи выглядит какPromise

  • xкак объект или функция

  • xдляPromise

Также обещание не может быть равно x, т.е.promise !== x,иначе:

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

Promise/A+резюме

Слишком далеко,Promise/A+Это то, что вам нужно знать. В основном включает терминологию и использование метода Then и соответствующие меры предосторожности. Особое внимание следует уделить обработке возвращаемого значения параметра в методе then. Затем мы используем TypeScript для реализации Promise на основе спецификации.

Далее, приведенные в тексте спецификации относятся конкретно кPromise/A+Технические характеристики

Обещание реализации

Promise сам по себе является конструктором, т.е. может быть реализован как класс. Затем основное внимание уделяется реализации класса Promise.

Давайте взглянем на свойства и методы следующего стандартного объекта обещания, чтобы вы знали, чего ожидать.

API, предоставляемые Promises:

Внутренние свойства обещания включают в себя:

Приступим к формальной части реализации

Файл декларации

Прежде чем мы начнем, давайте взглянем на некоторые объявления типов, используемые при написании промисов в TypeScript. могу видеть этоФайл декларации.

в основном включает:

Типприъемный заявленный файл для внешнего модуля является основной частью TypeyctScript. Кроме того, может быть использовано оператор из API файла обзора, в котором может быть выставлен модуль (принять, какой тип или какой тип данных будет возвращен). Эта API разработана заранее, чтобы разработать хорошую привычку, но фактическое развитие было бы сложнее.

Далее, давайте посмотрим на начало базовой реализации класса Promise — конструктора.

Конструктор

В спецификации упоминается, что конструктор Promise принимаетResolverФункция типа в качестве первого параметра принимает два параметра разрешения и отклонения, используемых для обработки состояния обещания.

Реализация выглядит следующим образом:

class Promise {
  // 内部属性
  private ['[[PromiseStatus]]']: PromiseStatus = 'pending';
  private ['[[PromiseValue]]']: any = undefined;

  subscribes: any[] = [];

  constructor(resolver: Resolver<R>) {
    this[PROMISE_ID] = id++;
    // resolver 必须为函数
    typeof resolver !== 'function' && resolverError();
    // 使用 Promise 构造函数,需要用 new 操作符
    this instanceof Promise ? this.init(resolver) : constructorError();
  }

  private init(resolver: Resolver<R>) {
    try {
      // 传入两个参数并获取用户传入的终值或拒因。
      resolver(
        value => {
          this.mockResolve(value);
        },
        reason => {
          this.mockReject(reason);
        }
      );
    } catch (e) {
      this.mockReject(e);
    }
    return null;
  }

  private mockResolve() {
    // TODO
  }
  private mockReject() {
    // TODO
  }
}

[[Решить]] реализация

Из предыдущего раздела спецификации мы узнали, что[[Resolve]]Принадлежит к внутренней реализации для обработки возвращаемой стоимости параметра затем. То есть, что собирается быть реализованным здесь, называетсяmockResolveМетоды.

По спецификации известно, чтоmockResolveЗначение, принимаемое методом, может быть Promise, thenable и другими допустимыми значениями JavaScript.

private mockResolve(value: any) {
  // 规范提到 resolve 不能传入当前返回的 promise
  // 即 `[[Resolve]](promise,x)` 中 promise !== x
  if (value === this) {
    this.mockReject(resolveSelfError);
    return;
  }
  // 非对象和函数,直接处理
  if (!isObjectORFunction(value)) {
    this.fulfill(value);
    return;
  }
  // 处理一些像 promise 的对象或函数,即 thenable
  this.handleLikeThenable(value, this.getThen(value));
}

Обработка объектов Thenable

Сфокусируйся наhandleLikeThenableРеализация, сочетание нескольких случаев, упомянутых спецификацией спереди для анализа:

  private handleLikeThenable(value: any, then: any) {
    // 处理 "真实" promise 对象
    if (this.isThenable(value, then)) {
      this.handleOwnThenable(value);
      return;
    }
    // 获取 then 值失败且抛出异常,则以此异常为拒因 reject promise
    if (then === TRY_CATCH_ERROR) {
      this.mockReject(TRY_CATCH_ERROR.error);
      TRY_CATCH_ERROR.error = null;
      return;
    }
    // 如果 then 是函数,则检验 then 方法的合法性
    if (isFunction(then)) {
      this.handleForeignThenable(value, then);
      return;
    }
    // 非 Thenable ,则将该终植直接交由 fulfill 处理
    this.fulfill(value);
  }

Обработать случай, когда Then в Thenable является функцией

В спецификации упоминается:

Если then это функция, вызовите ее с x в качестве области видимости функции this . Передайте две функции обратного вызова в качестве параметров, первый параметр называется resolvePromise, а второй параметр называется rejectPromise.

В настоящее время,handleForeignThenableОн используется для проверки метода then.

Реализация выглядит следующим образом:

  private tryThen(then, thenable, resolvePromise, rejectPromise) {
    try {
      then.call(thenable, resolvePromise, rejectPromise);
    } catch (e) {
      return e;
    }
  }
  private handleForeignThenable(thenable: any, then: any) {
    this.asap(() => {
      // 如果 resolvePromise 和 rejectPromise 均被调用,
      // 或者被同一参数调用了多次,则优先采用首次调用并忽略剩下的调用
      // 此处 sealed (稳定否),用于处理上诉逻辑
      let sealed = false;
      const error = this.tryThen(
        then,
        thenable,
        value => {
          if (sealed) {
            return;
          }
          sealed = true;
          if (thenable !== value) {
            this.mockResolve(value);
          } else {
            this.fulfill(value);
          }
        },
        reason => {
          if (sealed) {
            return;
          }
          sealed = true;
          this.mockReject(reason);
        }
      );

      if (!sealed && error) {
        sealed = true;
        this.mockReject(error);
      }
    });
  }

выполнить выполнение

приходите посмотреть[[Resolve]]последний шаг,fulfillвыполнить:

  private fulfill(value: any) {
    this['[[PromiseStatus]]'] = 'fulfilled';
    this['[[PromiseValue]]'] = value;

    // 用于处理异步情况
    if (this.subscribes.length !== 0) {
      this.asap(this.publish);
    }
  }

Увидев это, вы, возможно, заметили, что многие методы имеютprivateмодификаторы в TypeScript

Свойство или метод, оформленный в частном порядке, является закрытым и не может быть доступен за пределами класса, в котором он объявлен.

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

[[Resolve]]резюме

Пока что внутренний[[Resolve]]Это сбылось. Давайте рассмотрим,[[Resolve]]Используется для обработки следующих ситуаций

// 实例化构造函数,传入 resolve 的情况
const promise = Promise(resolve => {
  const value: any;
  resolve(value);
});

так же как

// then 方法中有 返回值的情况
promise.then(
  () => {
    const value: any;
    return value;
  },
  () => {
    const reason: any;
    return reason;
  }
);

для окончательной стоимостиvalueЕсть много случаев, когда вы имеете дело с Thenable, обратитесь к спецификации для достижения.promiseКромеresolveтакжеreject, но эта часть контента относительно проста, мы объясним это позже. Первый взгляд наresolveнеразлучныthenреализация метода. Это тожеpromiseметод ядра.

Затем реализация метода

С предыдущей реализацией мы уже можем изменить внутренности из конструктора Promise.[[PromiseStatus]]статус и внутреннее[[PromiseValue]]value, и у нас есть соответствующая совместимая обработка для различных значений value. Далее пришло время передать эти значения в первый параметр в методе thenonFulfilledобработанный.

Давайте посмотрим на эту ситуацию, прежде чем объяснять:

promise2 = promise1.then(onFulfilled, onRejected);

использоватьpromise1изthenПосле метода объект вернет обещаниеpromise2Реализация выглядит следующим образом:

class Promise {
  then(onFulfilled?, onRejected?) {
    // 对应上述的 promise1
    const parent: any = this;
    // 对应上述的 promise2
    const child = new parent.constructor(() => {});

    // 根据 promise 的状态选择处理方式
    const state = PROMISE_STATUS[this['[[PromiseStatus]]']];
    if (state) {
      // promise 各状态对应枚举值 'pending' 对应 0 ,'fulfilled' 对应 1,'rejected' 对应 2
      const callback = arguments[state - 1];
      this.asap(() =>
        this.invokeCallback(
          this['[[PromiseStatus]]'],
          child,
          callback,
          this['[[PromiseValue]]']
        )
      );
    } else {
      // 调用 then 方法的 promise 处于 pending 状态的处理逻辑,一般为异步情况。
      this.subscribe(parent, child, onFulfilled, onRejected);
    }

    // 返回一个 promise 对象
    return child;
  }
}

Здесь больше бросается в глазаasapО нем будет сказано отдельно позже. Давайте сначала разберемся с логикой.thenМетод принимает два параметра, которые задаются текущимpromiseГосударство решает позвонитьonFulfilledещеonRejected.

Теперь всех очень беспокоит, как выполняется код в методе THEN, например следующийconsole.log:

promise.then(value => {
  console.log(value);
});

Далее просмотрите соответствующиеinvokeCallbackметод

Обработка обратного вызова в методе then

в тогдашнем методеonFulfilledа такжеonRejectedВсе они являются необязательными параметрами.Прежде чем вы начнете дальнейшее объяснение, рекомендуется сначала понять характеристики двух параметров, упомянутых в спецификации.

Теперь, чтобы объяснитьinvokeCallbackПринимаемые параметры и их значения:

  • settled (стабильное состояние), обещание находится вне ожидаетсяСостояние называется урегулированным, а значение урегулированного может бытьfulfilledилиrejected

  • childобъект обещания, который будет возвращен

  • callbackсогласно сsettledВыбраноonFulfilledилиonRejectedПерезвоните

  • detailЗначение (окончательное значение) или причина (отказ) промиса текущего вызова метода then

Обратите внимание здесьsettledа такжеdetail,settledиспользуется для обозначенияfulfilledилиrejected, деталь используется для обозначенияvalueилиreasonвсе это имеет смысл

Зная это, нужно лишь обратиться к тому, как спецификация рекомендует реализовать соответствующую обработку:

  private invokeCallback(settled, child, callback, detail) {
    // 1、是否有 callback 的对应逻辑处理
    // 2、回调函数执行后是否会抛出异常,即相应处理
    // 3、返回值不能为自己的逻辑处理
    // 4、promise 结束(执行结束或被拒绝)前不能执行回调的逻辑处理
    // ...
  }

Логика для обработки дана, а остальные методы реализации читатели могут реализовать самостоятельно или посмотреть реализацию исходного кода этого проекта. Рекомендуется, чтобы все реализации ссылались на спецификацию для реализации, и во время реализации могут возникать пропуски или неправильная обработка. (ps: пришло время проверить надежность зависимости)

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

затем асинхронная обработка

Здесь нужно обработать случай, когда промис, вызвавший метод then, находится в состоянии ожидания.

Когда это произойдет? Взгляните на этот код:

const promise = new Promise(resolve => {
  setTimeout(() => {
    resolve(1);
  }, 1000);
});

promise.then(value => {
  console.log(value);
});

Код написан здесь, если это произойдет. Наше обещание на самом деле не работает. из-заsetTimeoutЭто ненормальная операция, когда внутренний метод then выполняется синхронно, разрешение вообще не выполняется, то есть вызывается обещание метода then.[[PromiseStatus]]в настоящее время «ожидает рассмотрения»,[[PromiseValue]]В настоящее время не определено, добавьте пару сейчасpendingОбратный вызов в состоянии не имеет смысла, и в спецификации упоминается, что обратный вызов метода then должен быть установлен(Я говорил это раньше) вызовет соответствующий обратный вызов.

Или нам не нужно рассматривать, вызвано ли это асинхронностью, просто нужно уточнить одну вещь. Есть такая ситуация, может быть вызвано состояние обещания метода thenpending.

В настоящее время должен быть механизм для решения этой ситуации, и соответствующая реализация кода:

  private subscribe(parent, child, onFulfillment, onRejection) {
    let {
      subscribes,
      subscribes: { length }
    } = parent;
    subscribes[length] = child;
    subscribes[length + PROMISE_STATUS.fulfilled] = onFulfillment;
    subscribes[length + PROMISE_STATUS.rejected] = onRejection;
    if (length === 0 && PROMISE_STATUS[parent['[[PromiseStatus]]']]) {
      this.asap(this.publish);
    }
  }

subscribeпринимает 4 аргументаparent,child,onFulfillment,onRejection

  • parentобъект обещания, для которого в данный момент вызывается метод then
  • childобъект обещания, который будет возвращен методом then
  • onFulfillmentПервый параметр метода THEN
  • onFulfillmentВторой параметр, затем метод

хранить в массивеsubscribe, который в основном сохраняет возвращаемый объект обещания и соответствующийonFulfillmentа такжеonRejectionПерезвоните.

удовлетворитьsubscribeэто новый случай и объект обещания, который вызывает метод then[[PromiseStatus]]значение не «ожидающее», затем вызовитеpublishметод. То есть в случае асинхронности это не будет называтьсяpublishметод. Кажется, что этоpublishМетод, связанный с выполнением обратного вызова.

В асинхронном случае, когда будет запущен обратный вызов?Вы можете просмотреть то, что я объяснил ранееfulfillметод:

  private fulfill(value: any) {
    this['[[PromiseStatus]]'] = 'fulfilled';
    this['[[PromiseValue]]'] = value;

    // 用于处理异步情况
    if (this.subscribes.length !== 0) {
      this.asap(this.publish);
    }
  }

когда удовлетворенthis.subscribes.length !== 0Когда триггерpublish.也就是说当异步函数执行完成后调用resolveметод, когда есть такой вызовsubscribesРешение функции обратного вызова внутри.

Это гарантируетthenФункция обратного вызова в методе будет запущена только после завершения выполнения асинхронной функции. Тогда посмотрите соответствующиеpublishметод

метод публикации

Прежде всего ясно, чтоpublishпубликуется, проходитinvokeCallbackдля вызова функции обратного вызова. В этом проекте только сsubscribesСвязанный. Просто посмотрите на код ниже:

  private publish() {
    const subscribes = this.subscribes;
    const state = this['[[PromiseStatus]]'];
    const settled = PROMISE_STATUS[state];
    const result = this['[[PromiseValue]]'];
    if (subscribes.length === 0) {
      return;
    }
    for (let i = 0; i < subscribes.length; i += 3) {
      // 即将返回的 promise 对象
      const item = subscribes[i];
      const callback = subscribes[i + settled];
      if (item) {
        this.invokeCallback(state, item, callback, result);
      } else {
        callback(result);
      }
    }
    this.subscribes.length = 0;
  }

затем краткое изложение метода

На данный момент мы реализовали метод then в обещании, что означает, что реализованное в настоящее время обещание имеет возможность обрабатывать асинхронные потоки данных. Реализация метода then неотделима от указаний спецификации.Пока вы ссылаетесь на описание метода then в спецификации, остальное — просто логическая обработка.

На данный момент основная функция обещания завершена, т. е. внутренняя[[Resolve]]а такжеthenметод. Давайте кратко рассмотрим остальную часть API.

Реализация API синтаксического сахара

catch и, наконец, являются синтаксическим сахаром

  • catchпринадлежатьthis.then(null, onRejection)

  • finallyпринадлежатьthis.then(callback, callback);

Обещание также обеспечиваетresolve,reject,all,raceСтатический метод , чтобы облегчить вызов цепочки, вышеупомянутые методы будут возвращать новый объект обещания для вызова цепочки.

В основном доresolve, а теперь посмотри

reject

rejectобработка сresolveНебольшое отличие состоит в том, что ему не нужно иметь дело с возможным случаем.В правиле упоминается, что значение reject является причиной.Рекомендуется реализовать код экземпляра ошибки следующим образом:

  private mockReject(reason: any) {
    this['[[PromiseStatus]]'] = 'rejected';
    this['[[PromiseValue]]'] = reason;
    this.asap(this.publish);
  }
  static reject(reason: any) {
    let Constructor = this;
    let promise = new Constructor(() => {});
    promise.mockReject(reason);
    return promise;
  }
  private mockReject(reason: any) {
    this['[[PromiseStatus]]'] = 'rejected';
    this['[[PromiseValue]]'] = reason;
    this.asap(this.publish);
  }

all & race

На базе предыдущего API не сложно все расширить и погонять. Давайте сначала посмотрим на роль двух:

  • all используется для обработки набора промисов, когда все промисы разрешаются или промис отклоняется, он возвращает массив значений или причину отклонения

  • Смысл гонки в том, чтобы посмотреть, кто самый быстрый в наборе промисов, затем использовать результат этого промиса

Соответствующий код реализации:

// all
let result = [];
let num = 0;
return new this((resolve, reject) => {
  entries.forEach(item => {
    this.resolve(item).then(data => {
      result.push(data);
      num++;
      if (num === entries.length) {
        resolve(result);
      }
    }, reject);
  });
});

// race
return new this((resolve, reject) => {
  let length = entries.length;
  for (let i = 0; i < length; i++) {
    this.resolve(entries[i]).then(resolve, reject);
  }
});

комбинация синхронизации

Если есть асинхронные функции, которые нужно выполнять последовательно, можно использовать следующие методы:

[func1, func2].reduce((p, f) => p.then(f), Promise.resolve());

Временная композиция в ES7 может быть выполнена с помощьюasync/awaitвыполнить

for (let f of [func1, func2]) {
  await f();
}

Дополнительные сведения об использовании см.это

дополнение к знаниям

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

  • Перезвоните

  • Асинхронный и синхронный

  • EventLoop

  • asap

Перезвоните

Английское определение функции обратного вызова:

Обратный вызов — это функция, которая передается в качестве аргумента другой функции и выполняется после завершения родительской функции.

Буквально функция обратного вызова является параметром, и эта функция передается в качестве параметра другой функции.Когда функция выполняется, выполняется переданная функция. Этот процесс называется обратным вызовом.

В JavaScript функция обратного вызова определяется следующим образом: функция A передается в качестве параметра (ссылки на функцию) другой функции B, и эта функция B выполняет функцию A. Предположим, что функция A называется функцией обратного вызова. Если имя (функция-выражение) отсутствует, она называется анонимной функцией обратного вызова.

Прежде всего, необходимо объявить, что callback-функция — это только реализация, а не конкретная реализация асинхронного режима. Функция обратного вызова также может использоваться в синхронных (блокирующих) сценариях и некоторых других сценариях.

Функции обратного вызова необходимо отличать от асинхронных функций.

Асинхронные функции и синхронные функции

  • Если вызывающая сторона может получить ожидаемый результат при возврате функции, это синхронная функция.

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

Так что же такое асинхронный и синхронный?

Асинхронный и синхронный

Прежде всего, должно быть понятно, что среда выполнения языка Javascript является «однопоточной». Так называемый «одиночный поток» означает, что одновременно может выполняться только одна задача. Если задач несколько, они должны быть поставлены в очередь, предыдущая задача завершена, следующая задача выполнена и так далее.

Этот режим вызовет проблему блокировки.Для решения этой проблемы язык Javascript делит режим выполнения задач на два типа: Синхронный и Асинхронный.

Но следует отметить, что:Асинхронный механизм завершается двумя или более резидентными потоками браузера вместе., однопоточность и асинхронность Javascript должны быть болееПринадлежность к поведению браузера. То есть сам Javascript является однопоточным,нет асинхронных функций.

Поскольку сценарием использования JavaScript является браузер, сам браузер является типичным рабочим потоком GUI.Рабочий поток GUI реализован как процесс обработки событий в большинстве систем, что позволяет избежать блокирующих взаимодействий, таким образом генерируя асинхронный ген JavaScript. Все методы и функции, использующие асинхронность, выполняются другим потоком браузера.

Темы в браузере

  • Поток триггера события браузераКогда событие запускается, поток добавляет событие в конец очереди ожидания, ожидая, пока механизм JavaScript обработает его. Эти события могут быть выполняемым в данный момент блоком кода, например:задача на времяили из других потоков ядра браузера, таких какнажмите,Асинхронный запрос AJAXд., но так как JavaScript однопоточный, все эти события должны ставиться в очередь для обработки движком JavaScript;

  • синхронизированный триггерный потоксчетчик таймера браузеранетПодсчитывается движком JavaScript, поскольку движок JavaScript является однопоточным, если он находится в состоянии заблокированного потока, это повлияет на точность синхронизации, поэтому более разумно использовать отдельный поток для синхронизации и запуска синхронизации;

  • Поток асинхронных HTTP-запросовПосле подключения XMLHttpRequest через браузер открывается новый запрос потока.Если установлена ​​функция обратного вызова, асинхронный поток сгенерирует событие изменения состояния и поместит его в очередь обработки движка JavaScript.ожидание обработки;

Благодаря вышеизложенному пониманию вы можете узнать, что JavaScript на самом делеАсинхронность достигается за счет взаимодействия и сотрудничества между потоком движка JS и другими потоками в браузере..

Но функция обратного вызова при добавлении к выполнению конкретного потока JS-движка? Какой порядок исполнения?

Тогда посмотрите на соответствующие механизмы с EventLoop

EventLoop

Давайте сначала рассмотрим некоторые понятия, стек, куча, очередь, прямо выше:

  • Те операции, которые не требуют функции обратного вызова, могут быть классифицированы как стек.

  • Куча используется для хранения объявленных переменных и объектов.

  • Как только асинхронная задача получит ответ, она будет помещена в очередь Queue.

Грубый процесс выглядит следующим образом:

Поток движка JS используется для выполненияСинхронизировать задачу, когда все задачи синхронизацииПосле выполнения стек очищается, затем прочитайте отложенную задачу в очереди сообщений и поместитеСоответствующая функция обратного вызова помещается в стек, один поток начинает выполнение новой задачи синхронизации.

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

Я не знаю, кто нарисовал эту картинку, она действительно великолепна! Давайте позаимствуем ее, чтобы описать общий процесс AJAX:

Запросы AJAX — это трудоемкие асинхронные операции, и в браузерах есть специальные потоки для их обработки. Асинхронные задачи запускаются при наличии кода, вызывающего AJAX в основном потоке. Работа по выполнению этой асинхронной задачи передается потоку AJAX,Основной поток не ожидает результата этой асинхронной операции.Но потом выполнить. Предположим, что код основного потока выполняется в определенное время, то есть стек в это время пуст. В более ранние моменты, после выполнения асинхронной задачи, сообщение сохранялось в Очереди, так что, когда Стек пуст, из него берется функция обратного вызова для выполнения.

То, что работает за этим, называетсяEventLoop, с приведенным выше общим пониманием. Давайте заново поймем EventLoop:

«Сторонником» асинхронности являются циклы событий. Асинхронность здесь следует точно называть циклами событий браузера или циклами событий среды выполнения javaScript, потому что в ECMAScript нет циклов событий, а циклы событий определены в стандарте HTML.

Цикл событий переводится как цикл событий, который можно понимать как способ достижения асинхронности Давайте взглянем на раздел определения цикла событий в стандарте HTML:

Чтобы координировать события, взаимодействие с пользователем, сценарии, рендеринг, работу в сети и т. д., пользовательские агенты должны использовать цикл обработки событий, описанный в этом разделе.

События, взаимодействие с пользователем, сценарии, рендеринг, сетевое взаимодействие — все это знакомые вещи, и все они координируются циклом событий. Запустите событие щелчка, сделайте запрос ajax, и за этим работает цикл обработки событий.

task

Цикл событий имеет одну или несколько очередей задач. Каждая задача исходит из определенного источника задачи.Например, для событий мыши и клавиатуры может быть предусмотрена очередь задач, а другие события представляют собой отдельную очередь. Для событий мыши и клавиатуры можно выделить больше времени, чтобы обеспечить плавное взаимодействие.

Задача также известна какmacrotask, очередь задач относительно проста для понимания, это очередь в порядке очереди, которая предоставляет задачи из указанного источника задач.

источник задачи задачи:

  • setTimeout
  • setInterval
  • setImmediate
  • I/O
  • UI rendering

microtask

Каждый цикл событий имеет очередь микрозадач, и микрозадача будет поставлена ​​в очередь в очередь микрозадач, а не в очередь задач. Очередь микрозадач чем-то похожа на очередь задач.Оба являются очередями в порядке очереди, а задачи предоставляются указанным источником задач.Разница в том, что в цикле событий существует только одна очередь микрозадач.

Источниками микрозадач обычно считаются:

  • process.nextTick
  • promises
  • Object.observe
  • MutationObserver

Вопрос из интервью о EventLoop

console.log('start');

setTimeout(function() {
  console.log('setTimeout');
}, 0);

Promise.resolve()
  .then(function() {
    console.log('promise1');
  })
  .then(function() {
    console.log('promise2');
  });

console.log('end');

// start
// end
// promise1
// promise2
// setTimeout

Приведенная выше последовательность выполняется в хроме, интересно протестировано в сафари 9.1.2, обещание1 обещание2 будет после setTimeout, а в сафари 10.0.1 получил тот же результат, что и в хроме. Разница между промисами в разных браузерах заключается в том, что некоторые браузеры помещают их в очередь макрозадач, а некоторые — в очередь микрозадач.

Сводка цикла событий

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

что как можно скорее

as soon as possibleАббревиатура в английском языке, роль обещания — как можно быстрее реагировать на изменения.

существуетОбещания/спецификация A+изNotes 3.1Упоминается, что тогдашний метод обещания может быть реализован с использованием механизма «макрозадачи» или механизма «микрозадачи».

В этом проекте с помощьюmacro-taskмеханизм

  private asap(callback) {
    setTimeout(() => {
      callback.call(this);
    }, 1);
  }

Или шаблон MutationObserver:

function flush() {
...
}
function useMutationObserver() {
  var iterations = 0;
  var observer = new MutationObserver(flush);
  var node = document.createTextNode('');
  observer.observe(node, { characterData: true });

  return function () {
    node.data = iterations = ++iterations % 2;
  };
}

первый раз смотрю этоuseMutationObserver 函数总会很有疑惑,MutationObserverРазве он не используется для наблюдения за изменениями в dom, так какой смысл создавать узел из ничего, чтобы многократно изменять его содержимое, чтобы вызвать наблюдаемую функцию обратного вызова?

Ответ заключается в использованииMutationСобытия могут выполнять операции асинхронно (функция сброса в примере), одно — реагировать на изменения как можно быстрее, а другое — исключать повторные вычисления.

Или в среде узла:

function useNextTick() {
  return () => process.nextTick(flush);
}

внешняя безопасность

Сейчас интерфейс находится в расцвете модульной разработки, столкнувшись сnpmИз этих тысяч «фирменных сумок» многие такие маленькие белые, как я, неизбежно потеряют себя и постепенно потеряют чувство безопасности.

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

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

Эпилог

Я много писал, и это утомительно. Я надеюсь быть полезным.

исходный код

Ссылаться на

Promises/A+ MDN Promises Обещания использования Event loop