30 минут на реализацию промиса, соответствующего спецификации (очень подробно)

Promise
30 минут на реализацию промиса, соответствующего спецификации (очень подробно)

предисловие

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

 

Роль обещаний

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

 

Предварительные знания

Шаблон наблюдателя

Что такое шаблон наблюдателя:

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

Promiseосновывается наШаблон проектирования наблюдателяосуществленный,thenФункция, которая будет выполняться функцией, будет помещена в массив наблюдателя, когдаPromiseПри изменении состояния выполняются все функции массива группы наблюдения.

механизм цикла событий

выполнитьPromiseвовлеченныйJavaScriptмеханизм цикла событий вEventLoop, а также концепции макрозадач и микрозадач.

Блок-схема механизма цикла событий выглядит следующим образом:

Вы можете посмотреть на этот код:

console.log(1);

setTimeout(() => {
  console.log(2);
},0);

let a = new Promise((resolve) => {
  console.log(3);
  resolve();
}).then(() => {
  console.log(4);
}).then(() => {
  console.log(5);
});

console.log(6);

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

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

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

 

Обещайте основные очки знаний

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

executor

// 创建 Promise 对象 x1
// 并在 executor 函数中执行业务逻辑
function executor(resolve, reject){
  // 业务逻辑处理成功结果
  const value = ...;
  resolve(value);
  // 失败结果
  // const reason = ...;
  // reject(reason);
}

let x1 = new Promise(executor);

первыйPromiseэто класс, который получает функцию выполненияexecutor, который принимает два параметра:resolveа такжеrejectЭти два параметраPromiseДве функции, определенные внутри, используются для изменения состояния и выполнения соответствующей функции обратного вызова.

потому чтоPromiseОн не знает, был ли результат выполнения неудачным или успешным. Он просто предоставляет контейнер для асинхронных операций. Фактический контроль находится в руках пользователя. Пользователь может вызвать два вышеуказанных параметра, чтобы сообщитьPromiseЯвляется ли результат успешным, и при этом бизнес-логика обрабатывает результат (value/reason) в качестве параметраresolveа такжеrejectДве функции, которые выполняют обратный вызов.

три состояния

PromiseЕсть три состояния:

  • pending:Ожидающий
  • resolved: удалось
  • rejected: не удалось

существуетPromiseЕсть только два возможных изменения состояния: отpendingсталиresolvedили изpendingсталиrejected, как показано ниже (цитата из мини-книги Promise):

引自 Promise 迷你书

И следует отметить, что после изменения состояния оно больше не изменится, и результат всегда будет одним и тем же. То есть, когда мыexecutorфункция называетсяresolveпосле, после звонкаrejectне влияет, и наоборот.

// 并在 executor 函数中执行业务逻辑
function executor(resolve, reject){
  resolve(100);
  // 之后调用 resolve,reject 都是无效的,
  // 因为状态已经变为 resolved,不会再改变了
  reject(100);
}

let x1 = new Promise(executor);

then

Каждыйpromiseвсе одноthenметод, это когдаpromiseПосле возврата результата функция обратного вызова, которую необходимо выполнить, имеет два необязательных параметра:

  • onFulfilled: успешный обратный вызов;
  • onRejected: неудачный обратный вызов;

Как показано ниже (цитата из мини-книги Promise):

引自 Promise 迷你书

// ...
let x1 = new Promise(executor);

// x1 延迟绑定回调函数 onResolve
function onResolved(value){
  console.log(value);
}

// x1 延迟绑定回调函数 onRejected
function onRejected(reason){
  console.log(reason);
}

x1.then(onResolved, onRejected);

 

Рукописное обещание общего процесса

Здесь мы просто пишем один вручнуюPromiseОбщий процесс:

исполнитель с тремя состояниями

  • new Promise, вам нужно пройтиexecutorфункция-исполнитель в конструкторе,Функция-исполнитель выполняется немедленно
  • executorФункция execute принимает два параметра:resolveа такжеreject
  • Promiseтолько отpendingприбытьrejected, или изpendingприбытьfulfilled
  • PromiseКак только состояние подтверждено, оно замораживается и не изменяется.

затем метод

  • всеPromiseимеютthenметод,thenПолучает два параметра, которыеPromiseуспешный обратный вызовonFulfilled, и неудачный обратный вызовonRejected
  • если звонишьthenчас,Promiseудалось, выполнитьonFulfilled, и воляPromiseЗначение передается как параметр; еслиPromiseне удалось, затем выполнитеonRejected, и воляPromiseПричина сбоя передается как параметр; еслиPromiseСтатусpending, нужно бытьonFulfilledа такжеonRejectedФункция сохраняется, ожидает определения состояния, а затем по очереди выполняет соответствующую функцию (режим наблюдателя)
  • thenпараметрыonFulfilledа такжеonRejectedВы не можете передать это,Promise Проникновение стоимости возможно.

Цепные вызовы и обработка, а затем возвращаемые значения

  • PromiseМогуthenнеоднократно,Promiseизthenметод возвращает новыйPromise.
  • еслиthenвозвращает нормальное значение, то этот результат (value) в качестве параметра, переданного следующемуthenУспешный обратный вызов (onFulfilled)
  • еслиthenвыдает исключение, то это исключение (reason) в качестве параметра, переданного следующемуthenнеудачный обратный вызов (onRejected)
  • еслиthenвозвращаетpromiseили другойthenableобъекта, то вам нужно дождаться этогоpromiseзакончить исполнение,promiseВ случае успеха переходите к следующемуthenобратный вызов успеха; если это не удается, перейти к следующемуthenобратный вызов с ошибкой.

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

Затем мы начнем реализовывать простейший пример, чтобы начать объяснение.

 

Первое издание (начиная с простого примера)

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

приходи 🌰

let p1 = new MyPromise((resolve, reject) => {
    setTimeout(() => {
      resolved('成功了');
    }, 1000);
})

p1.then((data) => {
    console.log(data);
}, (err) => {
    console.log(err);
})

Простой пример1sвернуться после成功了, И вthenна выходе.

выполнить

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

class MyPromise {
  // ts 接口定义 ...
  constructor (executor: executor) {
    // 用于保存 resolve 的值
    this.value = null;
    // 用于保存 reject 的值
    this.reason = null;
    // 用于保存 then 的成功回调
    this.onFulfilled = null;
    // 用于保存 then 的失败回调
    this.onRejected = null;

    // executor 的 resolve 参数
    // 用于改变状态 并执行 then 中的成功回调
    let resolve = value => {
      this.value = value;
      this.onFulfilled && this.onFulfilled(this.value);
    }

    // executor 的 reject 参数
    // 用于改变状态 并执行 then 中的失败回调
    let reject = reason => {
      this.reason = reason;
      this.onRejected && this.onRejected(this.reason);
    }

    // 执行 executor 函数
    // 将我们上面定义的两个函数作为参数 传入
    // 有可能在 执行 executor 函数的时候会出错,所以需要 try catch 一下 
    try {
      executor(resolve, reject);
    } catch(err) {
      reject(err);
    }
  }

  // 定义 then 函数
  // 并且将 then 中的参数复制给 this.onFulfilled 和 this.onRejected
  private then(onFulfilled, onRejected) {
    this.onFulfilled = onFulfilled;
    this.onRejected = onRejected;
  }
}

Что ж, наша первая версия завершена, не правда ли, она очень проста.

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

Приведенный выше пример хорош, потому чтоresolve(成功了)завернут вsetTimeout, он будет выполнен в следующей задаче макроса, когда функция обратного вызова будет зарегистрирована.

Каждый может попробоватьresolve(成功了)отsetTimeoutУбери его, в это время будут проблемы.

Существует проблема

Эта версия очень проста в реализации, но есть еще несколько проблем:

  • Понятие государства не введено.

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

  • Сцепленные вызовы не поддерживаются

Обычно мы можемPromiseВыполнение цепочек вызовов:

let p1 = new MyPromise((resolve, reject) => {
  setTimeout(() => {
    resolved('成功了');
  }, 1000);
})

p1.then(onResolved1, onRejected1).then(onResolved2, onRejected2)
  • Поддерживается только одна функция обратного вызова. Если функций обратного вызова несколько, последняя перезапишет первую.

В этом примереonResolved2будет охватыватьonResolved1,onRejected2будет охватыватьonRejected1.

let p1 = new MyPromise((resolve, reject) => {
  setTimeout(() => {
    resolved('成功了');
  }, 1000);
})

// 注册多个回调函数
p1.then(onResolved1, onRejected1);
p1.then(onResolved2, onRejected2);

Далее мы идем еще дальше и решаем эти проблемы.

 

Второе издание (реализация связанных вызовов)

В этой версии мы вводим понятие состояния и одновременно реализуем функцию цепного вызова.

плюс государство

Выше мы сказалиPromiseЕсть три состояния:pending,resovled,rejected, только изpendingПеревести вresovledилиrejected, и когда состояние изменяется, состояние не может быть изменено.

  • Мы определяем свойствоstatus: используется для записи текущегоPromiseположение дел
  • Во избежание ошибок определим состояние как константуPENDING,RESOLVED,REJECTED.
  • При этом мы сэкономимthenОбратный вызов успеха определяется как массив:this.resolvedQueuesа такжеthis.rejectedQueues, мы можем положитьthenФункции обратного вызова вставляются в соответствующий массив, что может решить третью проблему, о которой мы упоминали выше.
class MyPromise {
  private static PENDING = 'pending';
  private static RESOLVED = 'resolved';
  private static REJECTED = 'rejected';

  constructor (executor: executor) {
    this.status = MyPromise.PENDING;
    // ...

    // 用于保存 then 的成功回调数组
    this.resolvedQueues = [];
    // 用于保存 then 的失败回调数组
    this.rejectedQueues = [];

    let resolve = value => {
      // 当状态是 pending 是,将 promise 的状态改为成功态
      // 同时遍历执行 成功回调数组中的函数,将 value 传入
      if (this.status == MyPromise.PENDING) {
        this.value = value;
        this.status = MyPromise.RESOLVED;
        this.resolvedQueues.forEach(cb => cb(this.value))
      }
    }

    let reject = reason => {
      // 当状态是 pending 是,将 promise 的状态改为失败态
      // 同时遍历执行 失败回调数组中的函数,将 reason 传入
      if (this.status == MyPromise.PENDING) {
        this.reason = reason;
        this.status = MyPromise.REJECTED;
        this.rejectedQueues.forEach(cb => cb(this.reason))
      }
    }

    try {
      executor(resolve, reject);
    } catch(err) {
      reject(err);
    }
  }
}

Совершенствуйте функцию then

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

Теперь нам нужно запихнуть эти два свойства в два массива:resolvedQueuesа такжеrejectedQueues.

class MyPromise {
  // ...

  private then(onFulfilled, onRejected) {
    // 首先判断两个参数是否为函数类型,因为这两个参数是可选参数
    // 当参数不是函数类型时,需要创建一个函数赋值给对应的参数
    // 这也就实现了 透传
    onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
    onRejected = typeof onRejected === 'function' ? onRejected : reason => { throw reason}

    // 当状态是等待态的时候,需要将两个参数塞入到对应的回调数组中
    // 当状态改变之后,在执行回调函数中的函数
    if (this.status === MyPromise.PENDING) {
      this.resolvedQueues.push(onFulfilled)
      this.rejectedQueues.push(onRejected)
    }

    // 状态是成功态,直接就调用 onFulfilled 函数
    if (this.status === MyPromise.RESOLVED) {
      onFulfilled(this.value)
    }

    // 状态是成功态,直接就调用 onRejected 函数
    if (this.status === MyPromise.REJECTED) {
      onRejected(this.reason)
    }
  }
}

Некоторые примечания о функции then

  • при каких обстоятельствахthis.statusбудетpendingстатус, при каких обстоятельствах это будетresolvedусловие

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

// this.status 为 pending 状态
new MyPromise((resolve, reject) => {
  setTimeout(() => {
    resolve(1)
  }, 0)
}).then(value => {
  console.log(value)
})

// this.status 为 resolved 状态
new MyPromise((resolve, reject) => {
  resolve(1)
}).then(value => {
  console.log(value)
})
  • чтопроникнуть

Как код ниже, когдаthenКогда никакие параметры не передаются,PromiseБудет использовать внутренний метод, определенный по умолчанию, с передачей результата следующемуthen.

let p1 = new MyPromise((resolve, reject) => {
  setTimeout(() => {
    resolved('成功了');
  }, 1000);
})

p1.then().then((res) => {
  console.log(res);
})

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

Поддержка связанных вызовов

Поддержка цепочек вызовов на самом деле очень проста, нам нужно только датьthenФункция, наконец, возвращаетthisВот и все, поэтому цепные вызовы поддерживаются:

class MyPromise {
  // ...
  private then(onFulfilled, onRejected) {
    // ...
    return this;
  }
}

каждый звонокthenПосле этого мы все возвращаем текущий thisPromiseобъект, потому чтоPromiseобъект существуетthenметод, на этот раз мы просто реализуемPromiseпростой звонок.

В это время запустите вышеуказанноепроникнутьтестовый код.

Однако приведенный выше код по-прежнему имеет соответствующие проблемы, см. следующий код:

const p1 = new MyPromise((resolved, rejected) => {
  resolved('resolved');  
});

p1.then((res) => {
  console.log(res);
  return 'then1';
})
.then((res) => {
  console.log(res);
  return 'then2';
})
.then((res) => {
  console.log(res);
  return 'then3';
})

// 预测输出:resolved -> then1 -> then2
// 实际输出:resolved -> resolved -> resolved

Результат отличается от наших ожиданий, потому что мыthenвернулся вthisпредставляет собойp1,существуетnew MyPromiseПосле этого фактически состояние изменилось сpendingизменился наresolvedсостояние, после этого оно не изменится, поэтому вMyPromiseсерединаthis.valueзначение всегда былоresolved.

В этот момент мы должны посмотреть наthenОчки знаний о возвращаемых значениях.

затем вернуть значение

Фактическиthenвернет новыйPromiseобъект.

Взгляните на следующий код:

// 新创建一个 promise
const aPromise = new Promise(function (resolve) {
  resolve(100);
});

// then 返回的 promise
var thenPromise = aPromise.then(function (value) {
  console.log(value);
});

console.log(aPromise !== thenPromise); // => true

Из приведенного выше кода мы можем получитьthenметод возвращенPromiseуже не оригиналPromise, как показано ниже (цитата из мини-книги Promise):

引自 Promise 迷你书

promiseпоследовательные вызовы, за которыми следуетjQueryЦепочка звонков разная,jQueryОбъект, возвращаемый цепным вызовом, по-прежнему является исходным.jQueryобъект;PromiseНекоторые методы больше похожи на массив, напримерslice, возвращает новое значение после каждой операции.

Код модернизации

class MyPromise {
  // ...

  private then(onFulfilled, onRejected) {
    onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
    onRejected = typeof onRejected === 'function' ? onRejected : reason => {throw reason}

    // then 方法返回一个新的 promise
    const promise2 = new MyPromise((resolve, reject) => {
      // 成功状态,直接 resolve
      if (this.status === MyPromise.RESOLVED) {
        // 将 onFulfilled 函数的返回值,resolve 出去
        let x = onFulfilled(this.value);
        resolve(x);
      }

      // 失败状态,直接 reject
      if (this.status === MyPromise.REJECTED) {
        // 将 onRejected 函数的返回值,reject 出去
        let x = onRejected(this.reason)
        reject && reject(x);
      }

      // 等待状态,将 onFulfilled,onRejected 塞入数组中,等待回调执行
      if (this.status === MyPromise.PENDING) {
        this.resolvedQueues.push((value) => {
          let x = onFulfilled(value);
          resolve(x);
        })
        this.rejectedQueues.push((reason) => {
          let x = onRejected(reason);
          reject && reject(x);
        })
      }
    });
    return promise2;
  }
}

// 输出结果 resolved -> then1 -> then2

Существует проблема

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

Следующий код:

const p1 = new MyPromise((resolved, rejected) => {
  resolved('我 resolved 了');  
});

p1.then((res) => {
  console.log(res);
  return new MyPromise((resolved, rejected) => {
    setTimeout(() => {
      resolved('then1');
    }, 1000)
  });
})
.then((res) => {
  console.log(res);
  return new MyPromise((resolved, rejected) => {
    setTimeout(() => {
      resolved('then2');
    }, 1000)
  });
})
.then((res) => {
  console.log(res);
  return 'then3';
})

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

 

Третье издание (асинхронная цепочка)

В этой версии мы реализуемpromiseасинхронная цепочка вызовов.

идеи

Первый взглядthenсерединаonFulfilledа такжеonRejectedВозвращаемое значение:

// 成功的函数返回
let x = onFulfilled(this.value);

// 失败的函数返回
let x = onRejected(this.reason);

Как видно из поставленного выше вопроса,xможет бытьобщее значение, также может бытьPromiseОбъект, передача обычных значений, в котором мы находимсявторое изданиеЭто было решено, теперь нужно решить, когдаxвернутьPromiseкак обращаться с предметами.

Это на самом деле очень просто, когдаxЯвляетсяPromiseКогда объект является временем, нам нужно дождаться возвратаPromiseПри изменении статуса, после выполненияthenфункция, код выглядит следующим образом:

class MyPromise {
  // ...

  private then(onFulfilled, onRejected) {
    onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
    onRejected = typeof onRejected === 'function' ? onRejected : reason => { throw reason}

    // then 方法返回一个新的 promise
    const promise2 = new MyPromise((resolve, reject) => {
      // 成功状态,直接 resolve
      if (this.status === MyPromise.RESOLVED) {
        // 将 onFulfilled 函数的返回值,resolve 出去
        let x = onFulfilled(this.value);
        resolvePromise(promise2, x, resolve, reject);
      }

      // 失败状态,直接 reject
      if (this.status === MyPromise.REJECTED) {
        // 将 onRejected 函数的返回值,reject 出去
        let x = onRejected(this.reason)
        resolvePromise(promise2, x, resolve, reject);
      }

      // 等待状态,将 onFulfilled,onRejected 塞入数组中,等待回调执行
      if (this.status === MyPromise.PENDING) {
        this.resolvedQueues.push(() => {
          let x = onFulfilled(this.value);
          resolvePromise(promise2, x, resolve, reject);
        })
        this.rejectedQueues.push(() => {
          let x = onRejected(this.reason);
          resolvePromise(promise2, x, resolve, reject);
        })
      }
    });
    return promise2;
  }
}

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

const resolvePromise = (promise2, x, resolve, reject) => {
  if (x instanceof MyPromise) {
    const then = x.then;
    if (x.status == MyPromise.PENDING) {
      then.call(x, y => {
        resolvePromise(promise2, y, resolve, reject);
      }, err => {
        reject(err);
      })
    } else {
      x.then(resolve, reject);
    }
  } else {
    resolve(x);
  }
}

описание кода

resolvePromise

resolvePromiseПринимает четыре параметра:

  • promise2даthenвернулся вpromise;
  • xдаthenдва параметраonFulfilledилиonRejectedВозвращаемое значение типа неопределенно, это может быть обычное значение, это может бытьthenableобъект;
  • resolveа такжеrejectдаpromise2из.

затем вернуть тип значения

когдаxдаPromise, а его состояниеPendingстатус, еслиxЕсли выполнение прошло успешно, то переходим к рекурсивному вызовуresolvePromiseЭта функция будетxрезультат выполнения какresolvePromiseПередается второй параметр;

Если выполнение не удается, вызовите напрямуюpromise2изrejectметод.

 

Здесь мы в основном имеем полныйpromise, то нам нужноPromises/A+стандартизировать нашиPromise.

 

Канонические обещания

Автор предыдущих редакций кода в основном следовал спецификации, вот некоторые моменты, которые не соответствуют спецификации.

Спецификация затем (Spec 2.2)

thenсерединаonFulfilledа такжеonRejectedЕго нужно выполнять асинхронно, то есть поместить в асинхронную задачу для выполнения (спецификация 2.2.4)

выполнить

мы должныthenфункция черезsetTimeoutЗаверните его и поместите в задачу макроса, которая здесь задействованаjsизEventLoop, вы можете перейти к соответствующим статьям следующим образом:

class MyPromise {
  // ...

  private then(onFulfilled, onRejected) {
    // ...
    // then 方法返回一个新的 promise
    const promise2 = new MyPromise((resolve, reject) => {
      // 成功状态,直接 resolve
      if (this.status === MyPromise.RESOLVED) {
        // 将 onFulfilled 函数的返回值,resolve 出去
        setTimeout(() => {
          try {
            let x = onFulfilled(this.value);
            resolvePromise(promise2, x, resolve, reject);
          } catch(err) {
            reject(err);
          }
        })
      }

      // 失败状态,直接 reject
      if (this.status === MyPromise.REJECTED) {
        // 将 onRejected 函数的返回值,reject 出去
        setTimeout(() => {
          try {
            let x = onRejected(this.reason)
            resolvePromise(promise2, x, resolve, reject);
          } catch(err) {
            reject(err);
          }
        })
      }

      // 等待状态,将 onFulfilled,onRejected 塞入数组中,等待回调执行
      if (this.status === MyPromise.PENDING) {
        this.resolvedQueues.push(() => {
          setTimeout(() => {
            try {
              let x = onFulfilled(this.value);
              resolvePromise(promise2, x, resolve, reject);
            } catch(err) {
              reject(err);
            }
          })
        })
        this.rejectedQueues.push(() => {
          setTimeout(() => {
            try {
              let x = onRejected(this.reason)
              resolvePromise(promise2, x, resolve, reject);
            } catch(err) {
              reject(err);
            }
          })
        })
      }
    });
    return promise2;
  }
}

Используйте посылки Micro Cass

Но есть еще проблема, мы знаемPromise.thenэто микрозадача, теперь при использованииsetTimeoutПосле упаковки это эквивалентно тому, чтобы стать задачей макроса.Вы можете увидеть следующий пример:

var p1 = new MyPromise((resolved, rejected) => {
  resolved('resolved');
})

setTimeout(() => {
  console.log('---setTimeout---');
}, 0);

p1.then(res => {
  console.log('---then---');
})

// 正常 Promise:then -> setTimeout
// 我们的 Promise:setTimeout -> then

Порядок вывода не тот, причина в том, что текущийPromiseчерезsetTimeoutЗадача макроса завернута.

Мы можем улучшить это немного и использовать микрозаски для обертыванияonFulfilled,onRejected, часто используемые микрозадачиprocess.nextTick,MutationObserver,postMessageподождите, мы используем этоpostMessageПерепишите это:

// ...
if (this.status === MyPromise.RESOLVED) {
  // 将 onFulfilled 函数的返回值,resolve 出去
  // 注册一个 message 事件
  window.addEventListener('message', event => {
    const { type, data } =  event.data;

    if (type === '__promise') {
      try {
        let x = onFulfilled(that.value);
        resolvePromise(promise2, x, resolve, reject);
      } catch(err) {
        reject(err);
      }
    }
  });
  // 立马执行
  window.postMessage({
    type: '__promise',
  }, "http://localhost:3001");
}

// ...

Метод реализации очень прост, следимwindowизmessageсобытие, и сразу после этого запускаетpostMessageсобытие, на этот разthenФункция обратного вызова уже находится в очереди микрозадачи, давайте перезапустим пример, и вы увидите, что порядок вывода изменилсяthen -> setTimeout.

КонечноPromiseВнутренняя реализация определенно не так проста, здесь автор только дает идею, если интересно, можете изучить.

Спецификация функции resolvePromise (спецификация 2.3)

повторные цитаты

повторные цитаты, когдаxа такжеpromise2Если совпадает, то нужно сообщить об ошибке и повторить заявку. (Спецификация 2.3.1)

Потому что ожидание завершения себя никогда не бывает плодотворным.

const p1 = new MyPromise((resolved, rejected) => {
  resolved('我 resolved 了');  
});

const p2 = p1.then((res) => {
  return p2;
});

тип х

Примерно делится на следующие:

  • 2.3.2: когдаxЯвляетсяPromise, затем подождитеxПосле изменения состояния оно считается выполненным или неудавшимся (это также относится к2.3.3,потому чтоPromiseНа самом делеthenableобъект)
  • 2.3.3: Когдаxявляется объектом или функцией, т.е.thenableобъект, то этоx.thenтак какthen
  • 2.3.4: Когдаxне является объектом или функцией, непосредственноxкак параметрresolveвернуть.

Мы в основном смотрим2.3.3просто отлично, потому чтоPrmiseтакже принадлежатthenableобъект, что такоеthenableЧто с объектом?

Проще говоря, он имеетthenобъект/функция метода, всеPromiseобъектыthenableобъекты, но не всеthenableобъект неPromiseобъект. следующим образом:

let thenable = {
 then: function(resolve, reject) {
   resolve(100);
 }
}

согласно сxТип для обработки:

  • еслиxнетthenableобъект, звоните напрямуюPromise2изresolve,Будуxв результате успеха;

  • когдаxдаthenableобъект, который вызоветxизthenметод, вызов после успехаresolvePromiseфункция и выполнит результатyкак новыйxвходящийresolvePromise, до этогоxценность больше не являетсяthenableобъект; в случае сбоя он будет вызван напрямуюpromise2изreject.

if (x != null && (typeof x === 'object' || typeof x === 'function')) {
  if (typeof then === 'function') {
    then.call(x, (y) => {
      resolvePromise(promise2, y, resolve, reject);
    }, (err) => {
      reject(err);
    })
  }
} else {
  resolve(x);
}

позвонить только один раз

Технические характеристики(Promise/A+ 2.3.3.3.3) указывает, что если оба вызоваresolvePromiseа такжеrejectPromiseили выполняется несколько вызовов с одним и тем же параметром, первый вызов имеет приоритет, а все остальные вызовы игнорируются, что гарантирует выполнение только одного изменения состояния.

Мы определяемcalledзаполнитель, чтобы получитьthenНезависимо от того, выполнила ли функция соответствующую функцию, которая изменяет состояние, после выполнения она больше не будет выполняться, в основном для соответствия спецификации.

x является объектом обещания

еслиxдаPromiseобъект, на самом деле, когда выполняетсяresolveПосле функции она больше не будет выполнятьсяrejectфункция, она находится непосредственно в текущейPromiseОбъект заканчивается.

x - это объект, который можно использовать

когдаxобычныйthenableфункцию, можно выполнять одновременноresolveа такжеrejectфункция, которая может выполняться одновременноpromise2изresolveфункция иrejectфункцию, а на самом делеpromise2После изменения состояния соответствующее значение не изменится. На самом деле проблемы нет, следующий код:

// thenable 对像
{
 then: function(resolve, reject) {
   setTimeout(() => {
     resolve('我是thenable对像的 resolve');
     reject('我是thenable对像的 reject')
    })
 }
}

Полная решимостьОбещание

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

const resolvePromise = (promise2, x, resolve, reject) => {
  if(x === promise2){
    return reject(new TypeError('Chaining cycle detected for promise'));
  }
  let called;
  if (x != null && (typeof x === 'object' || typeof x === 'function')) {
    try {
      let then = x.then;
      if (typeof then === 'function') {
        then.call(x, y => {
          if(called)return;
          called = true;
          resolvePromise(promise2, y, resolve, reject);
        }, err => {
          if(called)return;
          called = true;
          reject(err);
        })
      } else {
        resolve(x);
      }
    } catch (e) {
      if(called)return;
      called = true;
      reject(e); 
    }
  } else {
    resolve(x);
  }
}

Вы сделали здесь, счастливы вы или нет!

Наконец мы можем запустить наш тестовый скриптMyPromiseсоответствовать спецификации.

тестовое задание

Есть специальные тестовые скрипты (promises-aplus-tests) может помочь нам проверить, соответствует ли код, который мы пишем,Promise/A+Технические характеристики.

Но, кажется, только тестjsфайл, так что автор будетtsфайл конвертируется вjsфайл, тест

Добавьте в код:

// 执行测试用例需要用到的代码
MyPromise.deferred = function() {
  let defer = {};
  defer.promise = new MyPromise((resolve, reject) => {
      defer.resolve = resolve;
      defer.reject = reject;
  });
  return defer;
}

Вам необходимо заранее установить тестовый плагин:

# 安装测试脚本
npm i -g promises-aplus-tests

# 开始测试
promises-aplus-tests MyPromise.js

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

Прошло отлично, дальше видноPromiseРеализовано больше методов.

 

больше способов

реализовать вышеуказанноеPromiseПосле этого относительно просто написать его экземпляр и статические методы.

метод экземпляра

Promise.prototype.catch

выполнить

По сути, этот методthenСинтаксический сахар для методов, просто дайтеthenпередачаonRejectedпараметрok.

private catch(onRejected) {
  return this.then(null, onRejected);
}
пример:
const p1 = new MyPromise((resolved, rejected) => {
  resolved('resolved');
})

p1.then((res) => {
  return new MyPromise((resolved, rejected) => {
    setTimeout(() => {
      rejected('错误了');
    }, 1000)
  });
})
.then((res) => {
  return new MyPromise((resolved, rejected) => {
    setTimeout(() => {
      resolved('then2');
    }, 1000)
  });
})
.then((res) => {
  return 'then3';
}).catch(error => {
  console.log('----error', error);
})

// 1s 之后输出:----error 错误了

Promise.prototype.finally

выполнить

finally()метод используется, чтобы указать, является лиPromiseОперация будет выполнена независимо от конечного состояния объекта.

private finally (fn) {
  return this.then(fn, fn);
}
пример
const p1 = new MyPromise((resolved, rejected) => {
  resolved('resolved');
})

p1.then((res) => {
  return new MyPromise((resolved, rejected) => {
    setTimeout(() => {
      rejected('错误了');
    }, 1000)
  });
})
.then((res) => {
  return new MyPromise((resolved, rejected) => {
    setTimeout(() => {
      resolved('then2');
    }, 1000)
  });
})
.then((res) => {
  return 'then3';
}).catch(error => {
  console.log('---error', error);
  return `catch-${error}`
}).finally(res => {
  console.log('---finally---', res);
})

// 输出结果:---error 错误了" -> ""---finally--- catch-错误了

 

статический метод

Promise.resolve

выполнить

Иногда необходимо преобразовать существующие объекты вPromiseобъект,Promise.resolve()метод делает именно это.

static resolve = (val) => {
  return new MyPromise((resolve,reject) => {
    resolve(val);
  });
}
пример
MyPromise.resolve({name: 'darrell', sex: 'boy' }).then((res) => {
  console.log(res);
}).catch((error) => {
  console.log(error);
});

// 输出结果:{name: "darrell", sex: "boy"}

Promise.reject

выполнить

Promise.reject(reason)метод также возвращает новыйPromiseэкземпляр, состояние экземпляраrejected.

static reject = (val) => {
  return new MyPromise((resolve,reject) => {
    reject(val)
  });
}
пример
MyPromise.reject("出错了").then((res) => {
  console.log(res);
}).catch((error) => {
  console.log(error);
});

// 输出结果:出错了

Promise.all

Promise.all()метод объединения несколькихPromiseэкземпляр, завернутый в новыйPromiseпример,

const p = Promise.all([p1, p2, p3]);
  • Толькоp1,p2,p3статус сталfulfilled,pгосударство станетfulfilled;
  • если толькоp1,p2,p3один из них былrejected,pсостояние становитсяrejected, в это время первыйrejectВозвращаемое значение экземпляра , будет передано вpфункция обратного вызова.
выполнить
static all = (promises: MyPromise[]) => {
  return new MyPromise((resolve, reject) => {
    let result: MyPromise[] = [];
    let count = 0;

    for (let i = 0; i < promises.length; i++) {
      promises[i].then(data => {
        result[i] = data;
        if (++count == promises.length) {
          resolve(result);
        }
      }, error => {
        reject(error);
      });
    }
  });
}
пример
let Promise1 = new MyPromise((resolve, reject) => {
  setTimeout(() => {
    resolve('Promise1');
  }, 2000);
});

let Promise2 = new MyPromise((resolve, reject) => {
  resolve('Promise2');
});

let Promise3 = new MyPromise((resolve, reject) => {
  resolve('Promise3');
})

let Promise4 = new MyPromise((resolve, reject) => {
  reject('Promise4');
})

let p = MyPromise.all([Promise1, Promise2, Promise3, Promise4]);

p.then((res) => {
  // 三个都成功则成功  
  console.log('---成功了', res);
}).catch((error) => {
  // 只要有失败,则失败 
  console.log('---失败了', err);
});

// 直接输出:---失败了 Promise4

Promise.race

Promise.race()Этот метод также заключается в переносе нескольких экземпляров Promise в новый экземпляр Promise.

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

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

выполнить
static race = (promises) => {
  return new Promise((resolve,reject)=>{
    for(let i = 0; i < promises.length; i++){
      promises[i].then(resolve,reject)
    };
  })
}
пример

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

// ...

let p = MyPromise.race([Promise1, Promise2, Promise3, Promise4])

p.then((res) => { 
  console.log('---成功了', res);
}).catch((error) => {
  console.log('---失败了', err);
});

// 直接输出:---成功了 Promise2

Promise.allSettled

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

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

Просто подождите, пока все эти экземпляры параметров вернут результаты, либоfulfilledещеrejected, и состояние метода может стать толькоfulfilled.

Этот методPromise.allРазница в том,allНевозможно определить, что все запросы завершились, потому что вall, если одинPromiseодеялоrejected,pсостояние сразу становитсяrejected, возможно, некоторые асинхронные запросы не были завершены.

выполнить
static allSettled = (promises: MyPromise[]) => {
  return new MyPromise((resolve) => {
    let result: MyPromise[] = [];
    let count = 0;
    for (let i = 0; i < promises.length; i++) {
      promises[i].finally(res => {
        result[i] = res;
        if (++count == promises.length) {
          resolve(result);
        }
      })
    }
  });
}
пример

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

let p = MyPromise.allSettled([Promise1, Promise2, Promise3, Promise4])

p.then((res) => {
  // 三个都成功则成功  
  console.log('---成功了', res);
}, err => {
  // 只要有失败,则失败 
  console.log('---失败了', err);
})

// 2s 后输出:---成功了 (4) ["Promise1", "Promise2", "Promise3", "Promise4"]

 

Суммировать

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

Наконец, ответив на несколько вопросов, вы сможете увидеть, насколько вы освоили его:

  • PromiseКак реализовать проникновение возвращаемого значения функции обратного вызова?
  • PromiseПосле ошибки, как вы прошлипузырьПерейти к последней функции, которая ловит исключение?
  • PromiseКак поддерживать связанные вызовы?
  • как будетPromise.thenПревратить это в микрозадачу?

Честно говоря, я хочу комплимент!

 

Справочная документация

 

образец кода

Пример кода можно найти здесь: