Выполнение обещаний от 0 до 1

внешний интерфейс JavaScript модульный тест Promise

предисловие

Промис должен быть знаком всем: асинхронный процесс JavaScript от исходного обратного вызова к промису, генератору и затем к наиболее часто используемому асинхронному/ожиданию (если вы не знакомы с ними, вы можете обратиться к другой моей статье).Асинхронное программирование JavaScript), это не только развитие технической реализации, но и прогресс в том, как управлять асинхронностью в идеологии. Обещания, как основа для последующих планов, являются главным приоритетом и являются наиболее часто задаваемыми вопросами во время интервью.

Сегодня мы реализуем Promise на основе спецификации A+ от 0 до 1. В процессе мы также обсудим обработку исключений Promise и возможность его завершения вручную. Наконец, мы проведем модульное тестирование реализованного Promise. Полный код загружен на github. Если вы хотите увидеть код напрямую, вы можетекликните сюда.

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

текст

1. Базовая структура

Когда new Promise() получает функцию-исполнитель в качестве параметра, функция будет выполнена немедленно.В функции есть два параметра, которые также являются функциями, а именно: разрешение и отклонение.Синхронное выполнение функции должно быть помещено в попытку. ..catch, иначе перехват ошибок невозможен.

MyPromise.js

function MyPromise(executor) {

  function resolve(value) {

  }

  function reject(reason) {
    
  }

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

module.exports = MyPromise;

resolve() получает значение значения успеха обещания, а reject получает причину отказа обещания.

test.js

let MyPromise = require('./MyPromise.js');

let promise = new MyPromise(function(resolve, reject) {
  resolve(123);
})

2. Добавьте конечный автомат

Проблемы с текущей реализацией:

  1. Обещание — это механизм конечного автомата, начальное состояниеpending, состояние успехаfulfilled, состояние отказаrejected. только отpending -> fulfilled, или изpending -> rejected, и как только состояние изменится, оно больше никогда не изменится.

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

MyPromise.js

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

function MyPromise(executor) {
  let self = this;
  self.state = PENDING;


  function resolve(value) {
    if (self.state === PENDING) {
      self.state = FULFILLED;
    }
  }

  function reject(reason) {
    if (self.state === PENDING) {
      self.state = REJECTED;
    }
  }

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

module.exports = MyPromise;

test.js

let MyPromise = require('./MyPromise.js');

let promise = new MyPromise(function(resolve, reject) {
  resolve(123);
});

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

3. Добавитьthenметод

Обещания имеютthenСпособ получения двух функцийonFulfilledа такжеonRejected, как обратные вызовы для Promise успеха и неудачи соответственно. Итак, вthenВ методе нам нужноstateсудить, еслиfulfilled, затем выполнитеonFulfilled(value)метод, еслиrejected, затем выполнитеonRejected(reason)метод.

Из-за ценности успехаvalueи причина отказаreasonпользователемexecutorпрошедшийresolve(value)а такжеreject(reason)передано, поэтому нам нужен глобальныйvalueа такжеreasonдоступны для последующих методов.

MyPromise.js

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

function MyPromise(executor) {
  let self = this;

  self.state = PENDING;
  self.value = null;
  self.reason = null;

  function resolve(value) {
    if (self.state === PENDING) {
      self.state = FULFILLED;
      self.value = value;
    }
  }

  function reject(reason) {
    if (self.state === PENDING) {
      self.state = REJECTED;
      self.reason = reason;
    }
  }

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

MyPromise.prototype.then = function(onFuifilled, onRejected) {
  let self = this;

  if (self.state === FULFILLED) {
    onFuifilled(self.value);
  }

  if (self.state === REJECTED) {
    onRejected(self.reason);
  }
};

module.exports = MyPromise;

4. Реализуйте асинхронные вызовы для разрешения

Проблемы с текущей реализацией:

  1. Синхронный вызовresolve()Нет проблем, но если это асинхронный вызов, например, установкаsetTimeout, потому что текущий код вызываетthen()Когда методstateещеpendingСтатус, вызываемый, когда таймер истекresolve()Пучокstateпревратиться вfulfilledстатус, ноonFulfilled()У функции истекло время для вызова.

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

MyPromise.js

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

function MyPromise(executor) {
  let self = this;

  self.state = PENDING;
  self.value = null;
  self.reason = null;
  self.onFulfilledCallbacks = [];
  self.onRejectedCallbacks = [];

  function resolve(value) {
    if (self.state === PENDING) {
      self.state = FULFILLED;
      self.value = value;

      self.onFulfilledCallbacks.forEach(function(fulfilledCallback) {
        fulfilledCallback();
      });
    }
  }

  function reject(reason) {
    if (self.state === PENDING) {
      self.state = REJECTED;
      self.reason = reason;

      self.onRejectedCallbacks.forEach(function(rejectedCallback) {
        rejectedCallback();
      });
    }
  }

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

MyPromise.prototype.then = function(onFuifilled, onRejected) {
  let self = this;

  if (self.state === PENDING) {
    self.onFulfilledCallbacks.push(() => {
        onFuifilled(self.value);
    });
    self.onRejectedCallbacks.push(() => {
        onRejected(self.reason);
    });
  }

  if (self.state === FULFILLED) {
    onFuifilled(self.value);
  }

  if (self.state === REJECTED) {
    onRejected(self.reason);
  }
};

module.exports = MyPromise;

Добавляем два массива callback-функцийonFulfilledCallbacksа такжеonRejectedCallbacks, хранитьthen()Обратные вызовы успеха и неудачи, переданные в методе. Затем, когда пользователь вызываетresolve()илиreject(), изменитьstateсостояние, а callback-функции последовательно вынимаются из соответствующего callback-массива для выполнения.

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

test.js

let MyPromise = require('./MyPromise.js');

let promise = new MyPromise(function(resolve, reject) {
  setTimeout(function() {
    resolve(123);
  }, 1000);
});

promise.then(function(value) {
  console.log('value1', value);
}, function(reason) {
  console.log('reason1', reason);
});

promise.then(function(value) {
  console.log('value2', value);
}, function(reason) {
  console.log('reason2', reason);
});

5. затем возвращает обещание

Учащиеся, прочитавшие спецификацию PromiseA+, должны знать, чтоthen()Метод возвращает все еще обещание и возвращает обещаниеresolveЗначение предыдущего промисаonFulfilled()функция илиonRejected()Возвращаемое значение функции. Если вы находитесь на предыдущем Обещанииthen()Если во время выполнения функции обратного вызова метода возникает ошибка, она будет перехвачена и использована в качестве возврата промиса.onRejectedПередаются параметры функции. Например:

let promise = new Promise((resolve, reject) => {
  resolve(123);
});

promise.then((value) => {
  console.log('value1', value);
  return 456;
}).then((value) => {
  console.log('value2', value);
});

let promise = new Promise((resolve, reject) => {
  resolve(123);
});

Результат печати:

value1 123
value2 456

let promise = new Promise((resolve, reject) => {
  resolve(123);
});

promise.then((value) => {
  console.log('value1', value);
  a.b = 2;    // 这里存在语法错误
  return 456;
}).then((value) => {
  console.log('value2', value);
}, (reason) => {
  console.log('reason2', reason);
});

Результат печати:

value1 123
reason2 ReferenceError: a is not defined

можно увидеть,then()Если в функции обратного вызова метода возникает ошибка, она будет перехвачена, а затемthen()Возвращенный Promise автоматически станетonRejected,воплощать в жизньonRejected()Перезвоните.

let promise = new Promise((resolve, reject) => {
  reject(123);
});

promise.then((value) => {
  console.log('value1', value);
  return 456;
}, (reason) => {
  console.log('reason1', reason);
  return 456;
}).then((value) => {
  console.log('value2', value);
}, (reason) => {
  console.log('reason2', reason);
});

Результат печати:

reason1 123
value2 456

Ладно, давай дальшеthen()Метод по-прежнему возвращает обещание.

MyPromise.js

MyPromise.prototype.then = function(onFuifilled, onRejected) {
  let self = this;
  let promise2 = null;

  promise2 = new MyPromise((resolve, reject) => {
    if (self.state === PENDING) {
      self.onFulfilledCallbacks.push(() => {
        try {
          let x = onFuifilled(self.value);
          self.resolvePromise(promise2, x, resolve, reject);
        } catch(reason) {
          reject(reason);
        }
      });
      self.onRejectedCallbacks.push(() => {
        try {
          let x = onRejected(self.reason);
          self.resolvePromise(promise2, x, resolve, reject);
        } catch(reason) {
          reject(reason);
        }
      });
    }
  
    if (self.state === FULFILLED) {
      try {
        let x = onFuifilled(self.value);
        self.resolvePromise(promise2, x, resolve, reject);
      } catch (reason) {
        reject(reason);
      }
    }
  
    if (self.state === REJECTED) {
      try {
        let x = onRejected(self.reason);
        self.resolvePromise(promise2, x, resolve, reject);
      } catch (reason) {
        reject(reason);
      }
    }
  });

  return promise2;
};

Как видите, мы добавилиpromise2так какthen()Возвращаемое значение метода. пройти черезlet x = onFuifilled(self.value)илиlet x = onRejected(self.reason)получатьthen()Возвращаемое значение функции обратного вызова метода, а затем вызовself.resolvePromise(promise2, x, resolve, reject), добавитpromise2,x,promise2изresolveа такжеrejectвходящий вresolvePromise()середина.

Итак, сосредоточимся наresolvePromise()метод.

MyPromise.js

MyPromise.prototype.resolvePromise = function(promise2, x, resolve, reject) {
  let self = this;
  let called = false;   // called 防止多次调用

  if (promise2 === x) {
    return reject(new TypeError('循环引用'));
  }

  if (x !== null && (Object.prototype.toString.call(x) === '[object Object]' || Object.prototype.toString.call(x) === '[object Function]')) {
    // x是对象或者函数
    try {
      let then = x.then;

      if (typeof then === 'function') {
        then.call(x, (y) => {
          // 别人的Promise的then方法可能设置了getter等,使用called防止多次调用then方法
          if (called) return ;
          called = true;
          // 成功值y有可能还是promise或者是具有then方法等,再次resolvePromise,直到成功值为基本类型或者非thenable
          self.resolvePromise(promise2, y, resolve, reject);
        }, (reason) => {
          if (called) return ;
          called = true;
          reject(reason);
        });
      } else {
        if (called) return ;
        called = true;
        resolve(x);
      }
    } catch (reason) {
      if (called) return ;
      called = true;
      reject(reason);
    }
  } else {
    // x是普通值,直接resolve
    resolve(x);
  }
};

resolvePromise()используется для разбораthen()То, что возвращается в функции обратного вызова, по-прежнему являетсяPromise,этоPromiseЭто может быть наша собственная библиотека, она может быть реализована другими библиотеками илиthen()объект метода, поэтому здесьresolvePromise()для единой обработки.

Далее переводится сСпецификация PromiseA+оresolvePromise()требования:


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

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

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

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

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

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

    • Если x находится в состоянии ожидания, обещание должно оставаться в ожидании до тех пор, пока x не будет выполнено или отклонено.
    • Если x выполняется, выполнить обещание с тем же значением
    • Если x находится в отклоненном состоянии, отклонить обещание по той же причине
  • 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, игнорируйте их.
        • В противном случае, согласно е отказывается обещать
      • Если then не является функцией, выполнить обещание с x в качестве аргумента
    • Если x не является объектом или функцией, выполнить обещание с x в качестве аргумента

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


Обратитесь к приведенным выше спецификациям в сочетании с комментариями в коде, я думаю, каждый может понятьresolvePromise()эффект.

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

test.js

let MyPromise = require('./MyPromise.js');

let promise = new MyPromise(function(resolve, reject) {
  setTimeout(function() {
    resolve(123);
  }, 1000);
});

promise.then((value) => {
  console.log('value1', value);
  return new MyPromise((resolve, reject) => {
    resolve(456);
  }).then((value) => {
    return new MyPromise((resolve, reject) => {
      resolve(789);
    })
  });
}, (reason) => {
  console.log('reason1', reason);
}).then((value) => {
  console.log('value2', value);
}, (reason) => {
  console.log('reason2', reason);
});

распечатать результат:

value1 123
value2 789

6. Пустьthen()Обратные вызовы методов всегда вызываются асинхронно

официальныйPromiseРеализованные функции обратного вызова всегда вызываются асинхронно:

console.log('start');

let promise = new Promise((resolve, reject) => {
  console.log('step-');
  resolve(123);
});

promise.then((value) => {
  console.log('step--');
  console.log('value', value);
});

console.log('end');

распечатать результат:

start
step-
end
step--
value1 123

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

MyPromise.js

MyPromise.prototype.then = function(onFuifilled, onRejected) {
  let self = this;
  let promise2 = null;

  promise2 = new MyPromise((resolve, reject) => {
    if (self.state === PENDING) {
      self.onFulfilledCallbacks.push(() => {
        setTimeout(() => {
          try {
            let x = onFuifilled(self.value);
            self.resolvePromise(promise2, x, resolve, reject);
          } catch (reason) {
            reject(reason);
          }
        }, 0);
      });
      self.onRejectedCallbacks.push(() => {
        setTimeout(() => {
          try {
            let x = onRejected(self.reason);
            self.resolvePromise(promise2, x, resolve, reject);
          } catch (reason) {
            reject(reason);
          }
        }, 0);
      });
    }
  
    if (self.state === FULFILLED) {
      setTimeout(() => {
        try {
          let x = onFuifilled(self.value);
          self.resolvePromise(promise2, x, resolve, reject);
        } catch (reason) {
          reject(reason);
        }
      }, 0);
    }
  
    if (self.state === REJECTED) {
      setTimeout(() => {
        try {
          let x = onRejected(self.reason);
          self.resolvePromise(promise2, x, resolve, reject);
        } catch (reason) {
          reject(reason);
        }
      }, 0);
    }
  });

  return promise2;
};

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

test.js

let MyPromise = require('./MyPromise.js');

console.log('start');

let promise = new MyPromise((resolve, reject) => {
  console.log('step-');
  setTimeout(() => {
    resolve(123);
  }, 1000);
});

promise.then((value) => {
  console.log('step--');
  console.log('value', value);
});

console.log('end');

распечатать результат:

start
step-
end
step--
value1 123

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

7. Реализацияcatch()метод

then()методonFulfilledа такжеonRejectedФункции обратного вызова передавать не требуется, если не передать, то мы не сможем получитьreject(reason)ошибка в , то мы можем позвонитьcatch()метод получения ошибок. Пример:

let promise = new Promise((resolve, reject) => {
  reject('has error');
});

promise.then((value) => {
  console.log('value', value);
}).catch((reason) => {
  console.log('reason', reason);
});

распечатать результат:

reason has error

Не только это,catch()Его можно использовать в качестве последнего шага вызова цепочки промисов, и ошибка, возникшая в предыдущем промисе, будет всплывать до последнего.catch()чтобы поймать исключение. Пример:

let promise = new Promise((resolve, reject) => {
  resolve(123);
});

promise.then((value) => {
  console.log('value', value);
  return new Promise((resolve, reject) => {
    reject('has error1');
  });
}).then((value) => {
  console.log('value', value);
  return new Promise((resolve, reject) => {
    reject('has error2');
  });
}).catch((reason) => {
  console.log('reason', reason);
});

распечатать результат:

value 123
reason has error1

Такcatch()Как реализуется метод?

Ответ в реализации Promise,onFulfilledа такжеonRejectedФункции имеют значения по умолчанию:

MyPromise.js

MyPromise.prototype.then = function(onFuifilled, onRejected) {
  onFuifilled = typeof onFuifilled === 'function' ? onFuifilled : value => {return value;};
  onRejected = typeof onRejected === 'function' ? onRejected : reason => {throw reason};
};

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

можно увидеть,onRejectedЗначение по умолчанию — поставить ошибкуreasonпройти черезthrowвыбросить. Поскольку мы выполняем синхронный код вtry...catch, поэтому, если обещание не выполнено, если оно не переданоonRejected, функция по умолчанию выдаст ошибкуreasonthrows, который затем перехватывается promise2, какreject(reason)разрешающая способность.

catch()Чтобы реализовать, нужно позвонитьthis.then(null, onRejected),из-заpromise2одеялоreject, поэтому выполнитonRejectedОбратный вызов, чтобы поймать ошибку первого промиса.

В заключение,then()метод не прошелonRejectedПерезвоните,PromiseВнутри это поможет вам написать функцию как обратный вызов по умолчанию, функцияthrowбросатьrejectилиtry...catchк ошибке, затем ошибкаreasonБудетpromise2так какreject(reason)прими решение, и оно будет следующимthen()методonRejectedФункция обратного вызова вызывается, аcatchтолько что написал спец.then(null, onRejected)Вот и все.

Итак, мы пишемPromiseКогда цепной вызов , вthen()нельзя пройти вonRejectedОбратный вызов, просто добавьте один в конце цепочки вызововcatch()Вот именно, чтобы в цепочкеPromiseВозникающие ошибки будутcatchзахвачен.

Пример 1:

let promise = new Promise((resolve, reject) => {
  reject(123);
});

promise.then((value) => {
  // 注意,不会走这里,因为第一个promise是被reject的
  console.log('value1', value);
  return new Promise((resolve, reject) => {
    reject('has error1');
  });
}).then((value) => {
  console.log('value2', value);
  return new Promise((resolve, reject) => {
    reject('has error2');
  });
}, (reason) => {
  // 注意,这个then有onRejected回调
  console.log('reason2', reason);
}).catch((reason) => {
  // 错误在上一个then就被捕获了,所以不会走到这里
  console.log('reason3', reason);
});

распечатать результат:

reason2 123

Пример 2:

let promise = new Promise((resolve, reject) => {
  reject(123);
});

promise.then((value) => {
  console.log('value1', value);
  return new Promise((resolve, reject) => {
    reject('has error1');
  });
}).then((value) => {
  console.log('value2', value);
  return new Promise((resolve, reject) => {
    reject('has error2');
  });
}).catch((reason) => {
  // 由于链条中的then都没有onRejected回调,所以会一直被冒泡到最后的catch这里
  console.log('reason3', reason);
});

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

распечатать результат:

reason3 123

8. Реализацияfinallyметод

finallyкакая-то библиотечная параPromiseМетод расширения, который реализует либоresolveещеreject, Пойдуfinallyметод.

MyPromise.js

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

9. Реализацияdoneметод

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

MyPromise.js

MyPromise.prototype.done = function() {
    this.catch(reason => {
        console.log('done', reason);
        throw reason;
    });
};

10. РеализацияPromise.allметод

Promise.all()получить один, содержащий несколькоPromiseмассив, когда всеPromiseобеfulfilledсостояние, возвращает массив результатов, порядок результатов в массиве и переданныеPromiseЗаказ один на один. если естьPromiseдляrejectedгосударство, весьPromise.allдляrejected.

MyPromise.js

MyPromise.all = function(promiseArr) {
  return new MyPromise((resolve, reject) => {
    let result = [];

    promiseArr.forEach((promise, index) => {
      promise.then((value) => {
        result[index] = value;

        if (result.length === promiseArr.length) {
          resolve(result);
        }
      }, reject);
    });
  });
};

test.js

let MyPromise = require('./MyPromise.js');

let promise1 = new MyPromise((resolve, reject) => {
  console.log('aaaa');
  setTimeout(() => {
    resolve(1111);
    console.log(1111);
  }, 1000);
});

let promise2 = new MyPromise((resolve, reject) => {
  console.log('bbbb');
  setTimeout(() => {
    reject(2222);
    console.log(2222);
  }, 2000);
});

let promise3 = new MyPromise((resolve, reject) => {
  console.log('cccc');
  setTimeout(() => {
    resolve(3333);
    console.log(3333);
  }, 3000);
});

Promise.all([promise1, promise2, promise3]).then((value) => {
  console.log('all value', value);
}, (reason) => {
  console.log('all reason', reason);
})

распечатать результат:

aaaa
bbbb
cccc
1111
2222
all reason 2222
3333

11. РеализацияPromise.raceметод

Promise.race()получить один, содержащий несколькоPromiseмассив , когда естьPromiseдляfulfilledсостояние, когда весь большойPromiseдляonfulfilledи выполнитьonFulfilledПерезвоните. если естьPromiseдляrejectedгосударство, весьPromise.raceдляrejected.

MyPromise.js

MyPromise.race = function(promiseArr) {
  return new MyPromise((resolve, reject) => {
    promiseArr.forEach(promise => {
      promise.then((value) => {
        resolve(value);   
      }, reject);
    });
  });
};

test.js

let MyPromise = require('./MyPromise.js');

let promise1 = new MyPromise((resolve, reject) => {
  console.log('aaaa');
  setTimeout(() => {
    resolve(1111);
    console.log(1111);
  }, 1000);
});

let promise2 = new MyPromise((resolve, reject) => {
  console.log('bbbb');
  setTimeout(() => {
    reject(2222);
    console.log(2222);
  }, 2000);
});

let promise3 = new MyPromise((resolve, reject) => {
  console.log('cccc');
  setTimeout(() => {
    resolve(3333);
    console.log(3333);
  }, 3000);
});

Promise.race([promise1, promise2, promise3]).then((value) => {
  console.log('all value', value);
}, (reason) => {
  console.log('all reason', reason);
})

распечатать результат:

aaaa
bbbb
cccc
1111
all reason 1111
2222
3333

12. РеализацияPromise.resolveметод

Promise.resolveсоздатьfulfilledидеальноPromise, как правило, размещается во всемPromiseначало цепочки, используемое для запускаPromiseцепь.

MyPromise.js

MyPromise.resolve = function(value) {
  let promise;

  promise = new MyPromise((resolve, reject) => {
    this.prototype.resolvePromise(promise, value, resolve, reject);
  });

  return promise;
};

test.js

let MyPromise = require('./MyPromise.js');

MyPromise.resolve(1111).then((value) => {
  console.log('value1', value);
  return new MyPromise((resolve, reject) => {
    resolve(2222);
  })
}).then((value) => {
  console.log('value2', value);
})

распечатать результат:

value1 1111
value2 2222

из-за входящегоvalueЭто может быть общее значение, это может бытьthenable, а возможно и другоеPromise, так что звонитеresolvePromiseразобрать.

12. РеализацияPromise.rejectметод

Promise.rejectсоздатьrejectedне удалосьPromise.

MyPromise.js

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

test.js

let MyPromise = require('./MyPromise.js');

MyPromise.reject(1111).then((value) => {
  console.log('value1', value);
  return new MyPromise((resolve, reject) => {
    resolve(2222);
  })
}).then((value) => {
  console.log('value2', value);
}).catch(reason => {
  console.log('reason', reason);
});

распечатать результат:

reason 1111

13. РеализацияPromise.deferredметод

Promise.deferredМожет использоваться для задержки выполненияresolveа такжеreject.

MyPromise.js

MyPromise.deferred = function() {
    let dfd = {};
    dfd.promies = new MyPromise((resolve, reject) => {
      dfd.resolve = resolve;
      dfd.rfeject = reject;
    });
    return dfd;
};

Таким образом, вы можете вызывать извнеdfd.resolve()а такжеdfd.reject()решатьPromise.

13. Как остановить одногоPromiseцепь

Предположим такой сценарий, у нас есть очень длинныйPromiseцепные вызовы, этиPromiseявляется последовательно зависимой связью, если одна из цепочекPromiseЕсли есть ошибка, нет необходимости выполнять вниз, по умолчанию мы не можем выполнить это требование, потому чтоPromiseБудь тоthenещеcatchвернетPromise, будет продолжать выполняться внизthenилиcatch. Пример:

new Promise(function(resolve, reject) {
  resolve(1111)
}).then(function(value) {
  // "ERROR!!!"
}).catch()
  .then()
  .then()
  .catch()
  .then()

Есть ли способ остановить этот цепной вызов после ОШИБКИ!!!, вообще не выполняя все функции обратного вызова после цепного вызова?

Мы инкапсулируем одинPromise.stopметод.

MyPromise.js

MyPromise.stop = function() {
  return new Promise(function() {});
};

stopвозвращает никогда не выполнявшийсяresolveилиrejectизPromise, то этоPromiseвсегда вpendingсостояние, поэтому никогда не выполняйте внизthenилиcatch. Итак, мы останавливаемPromiseцепь.

new MyPromise(function(resolve, reject) {
  resolve(1111)
}).then(function(value) {
  // "ERROR!!!"
  MyPromise.stop();
}).catch()
  .then()
  .then()
  .catch()
  .then()

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

14. Как исправитьPromiseпоследний вернулся по цепочкеPromiseПроизошла ошибка

См. следующий пример:

new Promise(function(resolve) {
  resolve(42)
}).then(function(value) {
  a.b = 2;
});

Здесь a не существует, поэтому присвоение значения a.b является синтаксической ошибкой,onFulfilledФункция обратного вызова завернута вtry...catchвыполняется, будет ошибкаcatchк, но так как нетthenилиcatch, эта ошибка не может быть обработана, она будетPromiseЕшьте, ничего необычного, так часто говорятОбещания могут поглощать ошибки.

Итак, как нам справиться с этой ситуацией?

метод первый

Это то, чего мы уже достигли.done().

new Promise(function(resolve) {
  resolve(42)
}).then(function(value) {
  a.b = 2;
}).done();

done()метод эквивалентенcatch, но больше не возвращаетсяPromiseда, обратите вниманиеdone()В методе не должно быть синтаксической ошибки, иначе его невозможно поймать.

Способ второй

прослушиватель общих ошибокwindowизerrorСобытия можно зафиксировать

window.addEventListener('error', error => {
  console.log(error); // 不会触发
});

Обещания неonRejected()Ошибки обработки необходимо отслеживатьunhandledrejectionмероприятие

window.addEventListener('unhandledrejection', error => {
  console.log('unhandledrejection', error); // 可以触发,而且还可以直接拿到 promise 对象
});

14. Модульное тестирование

конец

Соответствующие модульные тесты и полный код можно найти в моемgithubПосмотри, если тебе поможет, приходи на звезду~

单元测试

github

Добро пожаловать, чтобы обратить внимание на мой общедоступный номер

微信公众号

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

Спецификация PromiseA+