Можете ли вы написать обещание от руки? Да, я обещаю.

внешний интерфейс
Можете ли вы написать обещание от руки? Да, я обещаю.

предисловие

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

[Практическая серия] Маршрутизация внешнего интерфейса

[Серия упражнений] Принцип Вавилона

[Практика] Кэш браузера

Заинтересованные студенты могут подписаться[Серия упражнений]. Попросите звездочку и попросите подписаться~

Что такое обещания?

Обещание — важное понятие в асинхронном программировании JS, оно абстрагирует асинхронную обработку объектов и является одним из наиболее популярных решений для асинхронного программирования Javascript.

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

Открытый стандарт для реализации разумных интероперабельных обещаний JavaScript.

срок

  • решить (исполнить): Относится к серии операций, выполняемых при успешном выполнении промиса, таких как изменение состояния, выполнение обратного вызова. Несмотря на то, что в спецификации для обозначения разрешения используется слово «исполнение», разрешение используется для обозначения реализации обещаний в более поздних поколениях.

  • отклонять: Относится к последовательности действий, которые необходимо выполнить, когда обещание не выполняется.

  • Отказ (причина): причина отклонения, которая относится к значению, переданному обратному вызову отклонения, когда обещание отклонено.

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

  • Promise: обещание — это объект или функция с методом then, который ведет себя в соответствии с этой спецификацией.

  • thenable: это объект или функция, определяющая метод then, что в тексте переводится как «имеет метод then».

  • исключение: это значение, выбрасываемое с помощью оператора throw.

Базовые требования

Начнем с некоторых основных требований спецификации Promise/A+.

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

Текущее состояние промиса должно быть одним из трех состояний:Состояние ожидания (ожидание) Исполненное состояние (Выполнено)иОтклоненное состояние (Rejected).


const PENDING = 'pending';

const FULFILLED = 'fulfilled';

const REJECTED = 'rejected';

Состояние ожидания

Находясь в состоянии ожидания, обещание должно соответствовать следующим условиям:

  • Может быть перемещен в состояние выполнения или отклонения
 if (this.state === PENDING) {
     this.state = FULFILLED || REJECTED ;
 }

Исполненное состояние (Выполнено)

В состоянии выполнения обещание должно соответствовать следующим условиям:

  • Невозможно перейти в любое другое состояние

  • должно иметь неизменное конечное значение

 this.value = value;

Отклоненный

В состоянии отклонения обещание должно соответствовать следующим условиям:

  • Невозможно перейти в любое другое состояние

  • должна иметь непреложную причину

 this.reason = reason;

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

2. Затем метод

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

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

promise.then(onFulfilled, onRejected)

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

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

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

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

функция onFulfilled

Если onFulfilled является функцией:

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

  • Его нельзя вызвать, пока обещание не будет выполнено

  • Его нельзя вызывать более одного раза

атрибут onRejected

Если onRejected является функцией:

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

  • Его нельзя вызвать, пока обещание не будет отклонено

  • Его нельзя вызывать более одного раза

время вызова

onFulfilled и onRejected могут быть вызваны только тогда, когда стек среды выполнения содержит только код платформы.Примечание 1

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

Эта очередь событий может быть реализована с использованием механизма «макрозадач» или «микрозадач».

Поскольку код реализации промисов сам по себе является кодом платформы (примечание переводчика: то есть весь JavaScript), сам код может уже содержать очередь планирования задач при обработке программы.

запрос вызова

onFulfilled и onRejected должны вызываться как функции (т.е. без этого значения)

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

тогда метод может вызываться несколько раз одним и тем же промисом

  • Когда обещание успешно выполнено, все onFulfilled должны быть вызваны в том порядке, в котором они были зарегистрированы.

  • Когда обещание отклонено, все onRejected должны быть вызваны в порядке их регистрации.

Простая практика

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

во-первых

npm init 

// 测试实现是否符合 promises/A+ 规范

npm install promises-aplus-tests -D 

package.json

{
  "name": "ajpromise",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "promises-aplus-tests ./simple.js"
  },
  "author": "webfansplz",
  "license": "MIT",
  "devDependencies": {
    "promises-aplus-tests": "^2.1.2"
  }
}
    

simple.js

//Promise 的三种状态  (满足要求 -> Promise的状态)
const PENDING = 'pending';
const FULFILLED = 'fulfilled';
const REJECTED = 'rejected';

class AjPromise {
  constructor(fn) {
    //当前状态
    this.state = PENDING;
    //终值
    this.value = null;
    //拒因
    this.reason = null;
    //成功态回调队列
    this.onFulfilledCallbacks = [];
    //拒绝态回调队列
    this.onRejectedCallbacks = [];

    //成功态回调
    const resolve = value => {
      // 使用macro-task机制(setTimeout),确保onFulfilled异步执行,且在 then 方法被调用的那一轮事件循环之后的新执行栈中执行。
      setTimeout(() => {
        if (this.state === PENDING) {
          // pending(等待态)迁移至 fulfilled(执行态),保证调用次数不超过一次。
          this.state = FULFILLED;
          // 终值
          this.value = value;
          this.onFulfilledCallbacks.map(cb => {
            this.value = cb(this.value);
          });
        }
      });
    };
    //拒绝态回调
    const reject = reason => {
      // 使用macro-task机制(setTimeout),确保onRejected异步执行,且在 then 方法被调用的那一轮事件循环之后的新执行栈中执行。 (满足要求 -> 调用时机)
      setTimeout(() => {
        if (this.state === PENDING) {
          // pending(等待态)迁移至 fulfilled(拒绝态),保证调用次数不超过一次。
          this.state = REJECTED;
          //拒因
          this.reason = reason;
          this.onRejectedCallbacks.map(cb => {
            this.reason = cb(this.reason);
          });
        }
      });
    };
    try {
      //执行promise
      fn(resolve, reject);
    } catch (e) {
      reject(e);
    }
  }
  then(onFulfilled, onRejected) {
    typeof onFulfilled === 'function' && this.onFulfilledCallbacks.push(onFulfilled);
    typeof onRejected === 'function' && this.onRejectedCallbacks.push(onRejected);
    // 返回this支持then 方法可以被同一个 promise 调用多次
    return this;
  }
}

Таким образом выполняется простое обещание.

new AjPromise((resolve, reject) => {
  setTimeout(() => {
    resolve(2);
  }, 2000);
})
  .then(res => {
    console.log(res);
    return res + 1;
  })
  .then(res => {
    console.log(res);
  });

//output  

// delay 2s..
//  2 
//  3 

Теперь давайте посмотрим, полностью ли наша реализация соответствует спецификации promises/A+~

npm run test

GG, пройдена лишь малая часть тестовых случаев, большинство из них красные~

ОК, далее давайте продолжим разбираться в дальнейших требованиях спецификации promises/A+~

дальнейший запрос

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

1. Назад

  • 1. Затем метод должен возвращать объект обещания

  • 2. Если onFulfilled или onRejected возвращает значение x , запустите следующий процесс разрешения промисов: [[Resolve]](promise2, x)

  • 3. Если onFulfilled или onRejected выдает исключение e, promise2 должен отклонить выполнение и вернуть причину отклонения e.

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

  • 5. Если onRejected не является функцией, а promise1 отклоняется, promise2 должен отклоняться и возвращать ту же причину.

  • 6. Независимо от того, отклоняется или разрешается обещание1, обещание2 будет разрешено, и только при возникновении исключения оно будет отклонено.

Шаг за шагом мы совершенствуем тогдашний метод с помощью вышеуказанных требований.
1.

// 1.首先,then方法必须返回一个promise对象
  then(onFulfilled, onRejected) {
    let newPromise;
    return (newPromise = new AjPromise((resolve, reject) => {}));
  }
  then(onFulfilled, onRejected) {
    let newPromise;
    return (newPromise = new AjPromise((resolve, reject) => {
      // 2.如果 onFulfilled 或者 onRejected 返回一个值 x ,则运行下面的 Promise 解决过程:[[Resolve]](promise2, x)
      this.onFulfilledCallbacks.push(value => {
        let x = onFulfilled(value);
        //解决过程 resolvePromise
        resolvePromise(newPromise, x);
      });
      this.onRejectedCallbacks.push(reason => {
        let x = onRejected(reason);
        //解决过程 resolvePromise
        resolvePromise(newPromise, x);
      });
    }));
  }
  // 解决过程
  function resolvePromise() {
  //...
  }

  then(onFulfilled, onRejected) {
    let newPromise;
    return (newPromise = new AjPromise((resolve, reject) => {
      //  3.如果 onFulfilled 或者 onRejected 抛出一个异常 e ,则 promise2 必须拒绝执行,并返回拒因 e。
      this.onFulfilledCallbacks.push(value => {
        try {
          let x = onFulfilled(value);
          resolvePromise(newPromise, x);
        } catch (e) {
          reject(e);
        }
      });
      this.onRejectedCallbacks.push(reason => {
        try {
          let x = onRejected(reason);
          resolvePromise(newPromise, x);
        } catch (e) {
          reject(e);
        }
      });
    }));
  }

4,5.

  then(onFulfilled, onRejected) {  
    let newPromise;
    // 4.如果 onFulfilled 不是函数且 promise1 成功执行, promise2 必须成功执行并返回相同的值。
    onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
    // 5.如果 onRejected 不是函数且 promise1 拒绝执行, promise2 必须拒绝执行并返回相同的据因。
    onRejected =
      typeof onRejected === 'function'
        ? onRejected
        : reason => {
            throw reason;
          };
    return (newPromise = new AjPromise((resolve, reject) => {
      this.onFulfilledCallbacks.push(value => {
        try {
          let x = onFulfilled(value);
          resolvePromise(newPromise, x);
        } catch (e) {
          reject(e);
        }
      });
      this.onRejectedCallbacks.push(reason => {
        try {
          let x = onRejected(reason);
          resolvePromise(newPromise, x);
        } catch (e) {
          reject(e);
        }
      });
    }));
  }
  then(onFulfilled, onRejected) {
    let newPromise;

    onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
    onRejected =
      typeof onRejected === 'function'
        ? onRejected
        : reason => {
            throw reason;
          };
    // 2.2.6规范 对于一个promise,它的then方法可以调用多次.
    // 当在其他程序中多次调用同一个promise的then时 由于之前状态已经为FULFILLED / REJECTED状态,则会走以下逻辑,
    // 所以要确保为FULFILLED / REJECTED状态后 也要异步执行onFulfilled / onRejected ,这里使用setTimeout

    // 6.不论 promise1 被 reject 还是被 resolve 时 promise2 都会被 resolve,只有出现异常时才会被 rejected。
    // 由于在接下来的解决过程中需要调用resolve,reject进行处理,处理我们在调用处理过程时,传入参数
    if (this.state == FULFILLED) {  
      return (newPromise = new AjPromise((resolve, reject) => {
        setTimeout(() => {
          try {
            let x = onFulfilled(this.value);
            resolvePromise(newPromise, x, resolve, reject);
          } catch (e) {
            reject(e);
          }
        });
      }));
    }
    if (this.state == REJECTED) {
      return (newPromise = new AjPromise((resolve, reject) => {
        setTimeout(() => {
          try {
            let x = onRejected(this.reason);
            resolvePromise(newPromise, x, resolve, reject);
          } catch (e) {
            reject(e);
          }
        });
      }));
    }
    if (this.state === PENDING) {
      return (newPromise = new AjPromise((resolve, reject) => {
        this.onFulfilledCallbacks.push(value => {
          try {
            let x = onFulfilled(value);
            resolvePromise(newPromise, x, resolve, reject);
          } catch (e) {
            reject(e);
          }
        });
        this.onRejectedCallbacks.push(reason => {
          try {
            let x = onRejected(reason);
            resolvePromise(newPromise, x, resolve, reject);
          } catch (e) {
            reject(e);
          }
        });
      }));
    }
  }

ОК, полный затем метод выполнен. Я считаю, что благодаря вышеуказанной практике вы лучше понимаете требования к возврату.

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

Разрешение промисов — это абстрактная операция, которая принимает обещание и значение, которое мы обозначаем как [[Resolve]](promise, x), если x имеет метод then и выглядит как обещание, преобразователь пытается выполнить обещание. состояние x; в противном случае выполняется обещание со значением x.

Эта функция thenable делает реализацию Promise более общей: поскольку она предоставляет метод then, соответствующий протоколу Promise/A+, она также делает реализацию, соответствующую спецификации Promise/A+, совместимой с менее стандартизированными, но пригодными для использования. хорошее сосуществование.

Запуск [[Resolve]](promise, x) выполняется следующим образом:

1. x равно обещанию

Если обещание и x указывают на один и тот же объект, отклоните обещание с помощью TypeError.

2. х обещание

  • Если x является Promise , заставляет promise принять состояние x .

  • Если x является ожидаемым, обещание остается ожидаемым до тех пор, пока x не будет выполнено или отклонено.

  • Если x выполняется, выполните обещание с тем же значением.

  • Если x находится в отклоненном состоянии, отклоните промис по той же причине.

3. x является объектом или функцией

Если X - объект или функция:

  • Присвойте x.then затем.

  • Если принимает значение x.then выдает ошибку e , отклоните обещание на основании e .

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

    • Если resolvePromise вызывается со значением y, запустите [[Resolve]](promise, y)
    • Если rejectPromise вызывается с причиной r в качестве аргумента, отклонить обещание с причиной r
    • Если вызываются и resolvePromise, и rejectPromise, или несколько раз с одним и тем же параметром, первый вызов будет иметь приоритет, а остальные будут проигнорированы.
    • При вызове метод выдает исключение e:
      • Если уже были вызваны resolvePromise или rejectPromise, игнорируйте их.
      • В противном случае отклоните обещание с помощью e
    • Если then не является функцией, выполнить обещание с x в качестве аргумента
  • Если x не является объектом или функцией, выполнить обещание с x в качестве аргумента

Если обещание разрешается объектом в зацикленной цепочке thenable, а рекурсивная природа [[Resolve]](promise, thenable) заставляет его вызываться снова, описанный выше алгоритм попадет в бесконечную рекурсию. Хотя это и не требуется алгоритмом, донорам рекомендуется обнаруживать существование такой рекурсии и, если это так, отклонить обещание с помощью идентифицируемой ошибки TypeError.

1.x равно обещанию

function resolvePromise(promise2, x, resolve, reject) {
  //x 与 promise 相等 
  //如果从onFulfilled中返回的x 就是promise2 就会导致循环引用报错
  
  //如果 promise 和 x 指向同一对象,以 TypeError 为据因拒绝执行 promise
  if (x === promise2) {
    reject(new TypeError('循环引用'));
  }
}

2.x — это обещания.

function resolvePromise(promise2, x, resolve, reject) {
  if (x === promise2) {
    reject(new TypeError('循环引用'));
  }
  // x 为 Promise
  else if (x instanceof AjPromise) {
    // 如果 x 为 Promise ,则使 promise 接受 x 的状态
    // 如果 x 处于等待态, promise 需保持为等待态直至 x 被执行或拒绝
    if (x.state === PENDING) {
      x.then(
        y => {
          resolvePromise(promise2, y, resolve, reject);
        },
        reason => {
          reject(reason);
        }
      );
    } else {
      // 如果 x 处于执行态,用相同的值执行 promise
      // 如果 x 处于拒绝态,用相同的据因拒绝 promise
      x.then(resolve, reject);
    }
  }
}

3.x — это объект или функция

function resolvePromise(promise2, x, resolve, reject) {
  if (x === promise2) {
    reject(new TypeError('循环引用'));
  }
  if (x instanceof AjPromise) {
    if (x.state === PENDING) {
      x.then(
        y => {
          resolvePromise(promise2, y, resolve, reject);
        },
        reason => {
          reject(reason);
        }
      );
    } else {
      x.then(resolve, reject);
    }
  } else if (x && (typeof x === 'function' || typeof x === 'object')) {
    // 避免多次调用
    let called = false;
    try {
      //把 x.then 赋值给 then
      let then = x.then;
      if (typeof then === 'function') {
        // 如果 then 是函数,将 x 作为函数的作用域 this 调用之。
        // 传递两个回调函数作为参数,第一个参数叫做 resolvePromise ,第二个参数叫做 rejectPromise
        // 如果 resolvePromise 和 rejectPromise 均被调用,或者被同一参数调用了多次,则优先采用首次调用并忽略剩下的调用
        then.call(
          x,
          // 如果 resolvePromise 以值 y 为参数被调用,则运行[[Resolve]](promise, y)
          y => {
            if (called) return;
            called = true;
            resolvePromise(promise2, y, resolve, reject);
          },
          // 如果 rejectPromise 以据因 r 为参数被调用,则以据因 r 拒绝 promise
          r => {
            if (called) return;
            called = true;
            reject(r);
          }
        );
      }else {
        // 如果 then 不是函数,以 x 为参数执行 promise
        resolve(x);
      }  
    } catch (e) {
      // 如果取 x.then 的值时抛出错误 e ,则以 e 为据因拒绝 promise
      // 如果调用 then 方法抛出了异常 e:
      // 如果 resolvePromise 或 rejectPromise 已经被调用,则忽略之
      // 否则以 e 为据因拒绝 promise
      if (called) return;
      called = true;
      reject(e);
    }
  } else {
    // 如果 x 不为对象或者函数,以 x 为参数执行 promise
    resolve(x);
  }
}

Хорошо~ Более сложный процесс решения также сделан за нас.Далее мы интегрируем код

Полный код Promises/A+ Specification


const PENDING = 'pending';
const FULFILLED = 'fulfilled';
const REJECTED = 'rejected';

class AjPromise {
  constructor(fn) {
    this.state = PENDING;
    this.value = null;
    this.reason = null;
    this.onFulfilledCallbacks = [];
    this.onRejectedCallbacks = [];
    const resolve = value => {
      if (value instanceof Promise) {
        return value.then(resolve, reject);
      }
      setTimeout(() => {
        if (this.state === PENDING) {
          this.state = FULFILLED;
          this.value = value;
          this.onFulfilledCallbacks.map(cb => {
            cb = cb(this.value);
          });
        }
      });
    };
    const reject = reason => {
      setTimeout(() => {
        if (this.state === PENDING) {
          this.state = REJECTED;
          this.reason = reason;
          this.onRejectedCallbacks.map(cb => {
            cb = cb(this.reason);
          });
        }
      });
    };
    try {
      fn(resolve, reject);
    } catch (e) {
      reject(e);
    }
  }
  then(onFulfilled, onRejected) {
    let newPromise;

    onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
    onRejected =
      typeof onRejected === 'function'
        ? onRejected
        : reason => {
            throw reason;
          };
    if (this.state === FULFILLED) {
      return (newPromise = new AjPromise((resolve, reject) => {
        setTimeout(() => {
          try {
            let x = onFulfilled(this.value);
            resolvePromise(newPromise, x, resolve, reject);
          } catch (e) {
            reject(e);
          }
        });
      }));
    }
    if (this.state === REJECTED) {
      return (newPromise = new AjPromise((resolve, reject) => {
        setTimeout(() => {
          try {
            let x = onRejected(this.reason);
            resolvePromise(newPromise, x, resolve, reject);
          } catch (e) {
            reject(e);
          }
        });
      }));
    }
    if (this.state === PENDING) {
      return (newPromise = new AjPromise((resolve, reject) => {
        this.onFulfilledCallbacks.push(value => {
          try {
            let x = onFulfilled(value);
            resolvePromise(newPromise, x, resolve, reject);
          } catch (e) {
            reject(e);
          }
        });
        this.onRejectedCallbacks.push(reason => {
          try {
            let x = onRejected(reason);
            resolvePromise(newPromise, x, resolve, reject);
          } catch (e) {
            reject(e);
          }
        });
      }));
    }
  }
}
function resolvePromise(promise2, x, resolve, reject) {
  if (x === promise2) {
    reject(new TypeError('循环引用'));
  }
  if (x instanceof AjPromise) {
    if (x.state === PENDING) {
      x.then(
        y => {
          resolvePromise(promise2, y, resolve, reject);
        },
        reason => {
          reject(reason);
        }
      );
    } else {
      x.then(resolve, reject);
    }
  } else if (x && (typeof x === 'function' || typeof x === 'object')) {
    let called = false;
    try {
      let then = x.then;
      if (typeof then === 'function') {
        then.call(
          x,
          y => {
            if (called) return;
            called = true;
            resolvePromise(promise2, y, resolve, reject);
          },
          r => {
            if (called) return;
            called = true;
            reject(r);
          }
        );
      } else {
        resolve(x);
      }
    } catch (e) {
      if (called) return;
      called = true;
      reject(e);
    }
  } else {
    resolve(x);
  }
}

AjPromise.deferred = function() {
  let defer = {};
  defer.promise = new AjPromise((resolve, reject) => {
    defer.resolve = resolve;
    defer.reject = reject;
  });
  return defer;
};

module.exports = AjPromise;

Давайте посмотрим, соответствует ли наша реализация спецификации Promises/A+.

npm run test

отлично, все тесты пройдены!

Адрес источника

портал

Если вы думаете, что это поможет вам, пожалуйста, поставьте звезду, чтобы поддержать автора~

использованная литература

Обещания/A+ канонический перевод

Детали обещания и реализация