Исходный код промиса: реализация простого промиса

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

предисловие

Промисы — это новый встроенный объект, добавленный в ES6 как решение, позволяющее избежать ада обратных вызовов.

От вложенных функций обратного вызова в прошлом до использования промисов для цепочки асинхронных обратных вызовов. Как именно реализован Promise, чтобы добиться «сглаживания» функции обратного вызова?

Далее давайте шаг за шагом реализуем простой Promise. Начинаю водить...

Шаги

Начнем с простого примера использования промисов:

var p = new Promise(function a (resolve) {
  console.log(1);
  setTimeout(function () {
    resolve(2);
  }, 1000);
})

p.then(function b (val) {
  console.log(val);
});

Код выполняется, он выполняет функцию первой и печатает 1. Таймер выполняет разрешение через 1 секунду, а затем выполняет функцию b.

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

  1. new Promise, выполнить конструктор Promise;
  2. В конструкторе выполните функцию a;
  3. выполнить функцию then;
  4. Через 1 секунду выполните функцию разрешения;
  5. Выполните функцию b.

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

начать упаковку

Это определяет MyPromise, который имеет функцию then иcallbackСвойство используется для хранения «функции b» выше.

function MyPromise (fn) {
  var _this = this;
  
  // 用来保存 then 传入的回调函数
  this.callback = undefined;
  
  function resolve (val) {
    _this.callback && _this.callback(val);
  }
  
  fn(resolve);
}

MyPromise.prototype.then = function (cb) {
  this.callback = cb;
};

Протестируйте с помощью:

var p = new MyPromise(function (resolve) {
  console.log(1);
  setTimeout(function () {
    resolve(2);
  }, 1000);
});

p.then(function (val) {
  console.log(val);
});

Когда код выполняется, он печатает 1 сразу и 2 через 1 секунду. проблема с волосами.

Обработка нескольких вызовов разрешения

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

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

var p = new MyPromise(function (resolve) {
  console.log(1);
  setTimeout(function () {
    resolve(2);
    resolve(3);
    resolve(4);
  }, 1000);
});

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

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

function MyPromise (fn) {
  var _this = this;

  this.callback = undefined;
  this.isResolved = false;
  
  function resolve (val) {
    if (_this.isResolved) return;
    _this.isResolved = true;
    _this.callback && _this.callback(val);
  }
  
  fn(resolve);
}

Несколько, затем обработка

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

var p = new MyPromise(function (resolve) {
  console.log(1);
  setTimeout(function () {
    resolve(2);
  }, 1000);
});

p.then(function (val) {
  console.log(val);
});

p.then(function (val) {
 console.log(val);
});

В отличие от разрешения, каждая функция обратного вызова, переданная в then, будет выполнена через 1 секунду, то есть тогда функции действительны. Код выполняется, сначала печатается 1. Через 1 секунду печатаются две двойки.

Поэтому свойство обратного вызова MyPromise необходимо изменить на数组Формат, сохраняет каждую функцию обратного вызова.

function MyPromise (fn) {
  var _this = this;

  this.callback = [];
  this.isResolved = false;
  
  function resolve (val) {
    if (_this.isResolved) return;
    _this.isResolved = true;
    
    if (_this.callback.length > 0) {
      _this.callback.forEach(function (func) {
        func && func(val);
      });
    }
  }
  
  fn(resolve);
}

MyPromise.prototype.then = function (cb) {
  this.callback.push(cb);
};

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

Поддерживает последовательные вызовы

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

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

var p = new MyPromise(function (resolve) {
  console.log(1);
  setTimeout(function () {
    resolve(2);
  }, 1000);
});

p.then(function (val) {
  console.log(val);
}).then(function (val) {
  console.log(val);
});

Поскольку я хочу иметь возможность вызывать then в цепочке, я могу вернуть this после выполнения функции then. Но разве это не то же самое, что код только что?

p.then(function (val) {
  console.log(val);
});

p.then(function (val) {
  console.log(val);
});

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

Давайте подумаем, что еще можно вернуть и с помощью функции then. Ответ заключается в новом новом MyPromise и возврате. Нам нужно переопределить реализацию функции then.

MyPromise.prototype.then = function (cb) {
  var _this = this;

  return new MyPromise(function (resolve) {
    _this.callback.push({
      cb: cb,
      resolve: resolve
    });
  });
};

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

В то же время разрешение в конструкторе MyPromise также необходимо скорректировать:

function MyPromise (fn) {
  var _this = this;

  this.callback = [];
  this.isResolved = false;
  
  function resolve (val) {
    if (_this.isResolved) return;
    _this.isResolved = true;

    if (_this.callback.length > 0) {
       _this.callback.forEach(function (item) {
       var res;
       var cb = item.cb;
       var resolve = item.resolve;
       
       cb && (res = cb(val));
       resolve && resolve(res);
     });
    }
  }
  
  fn(resolve);
}

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

p.then(function (val) {
  console.log(val);
  return val + 1;
}).then(function(val) {
  console.log(val);
});

Таким образом, вы можете связать затем вызовы. Не волнуйтесь, то, что мы реализуем, только тогда同步цепочки вызовов, и в конечном итоге мы хотим异步цепные вызовы.

Нам нужно что-то вроде этого:

p.then(function (val) {
  console.log(val);
  
  return new MyPromise(function (resolve) {
    setTimeout(function () {
      resolve(val + 1);
    }, 1000);  
  });
}).then(function(val) {
  console.log(val);
});

Сначала напечатайте 1, затем напечатайте 2 в первом, затем через 1 секунду, а затем выведите 3 во втором, а затем через одну секунду.

Следовательно, нам нужно судить, вынимая значение, возвращаемое первоначально сохраненным cb. Если значение является объектом MyPromise, то вызывается его then, в противном случае вызывается как раньше.

function MyPromise (fn) {
  var _this = this;

  this.callback = [];
  this.isResolved = false;
  
  function resolve (val) {
    if (_this.isResolved) return;
    _this.isResolved = true;

    if (_this.callback.length > 0) {
      _this.callback.forEach(function (item) {
        var res;
        var cb = item.cb;
        var resolve = item.resolve;
        
        cb && (res = cb(val));
        if (typeof res === 'object' && res.then) {
          res.then(resolve);
        } else {
          resolve && resolve(res);
        }
      });
    }
  }
  
  fn(resolve);
}

наконец

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

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

Таким образом, реализуется простое обещание, полный штамп кодаздесь.