Эта статья является первой из серии обучающих колес вместе. В этой статье мы начнем с нуля, чтобы написать промис, соответствующий спецификации Promises/A+. В этой серии статей мы выберем некоторые классические передние колеса для исходного кода. анализ и начните с нуля шаг за шагом. Реализация. В этой серии вы узнаете, как реализовать классические интерфейсные колеса, такие как Promises/A+, Redux, react-redux, vue, dom-diff, webpack, babel, kao, express, async. /await, jquery, Lodash, requirejs, lib-flexible и т. д., исходный код каждой главы размещен на github, добро пожаловать на внимание~
Похожие серии статей:
Научитесь строить колеса вместе (1): с нуля напишите обещание, соответствующее спецификации Promises/A+.
Научитесь собирать колеса вместе (2): напишите Redux с нуля
Научитесь строить колеса вместе (3): напишите React-Redux с нуля
Эта серия репозиториев github:
Давайте вместе изучим серию колес на github (добро пожаловать, звезда~)
предисловие
Промисы — это решение для асинхронного программирования, которое является более разумным и мощным, чем традиционные решения с функциями обратного вызова и событиями. Впервые он был предложен и реализован сообществом, ES6 вписал его в стандарт языка, унифицированное использование и изначально предоставил объекты Promise. Эта статья не посвящена объяснению использования промисов, для использования вы можете посмотреть раздел промисов в серии ECMAScript 6 г-на Руана Ифэна:
В этой статье в основном объясняется, как реализовать возможности и функции обещания шаг за шагом с нуля и, наконец, привести его в соответствие со спецификацией Promises/A+.Поскольку объяснение относительно подробное, статья немного длиннее.
Кроме того, исходный код проекта каждого шага находится на github, вы можете обратиться к нему, каждый шаг имеет соответствующий код проекта и тестовый код, если вам это нравится, добро пожаловать, чтобы дать звезду ~
адрес проекта:Репозиторий кода этой статьи на github
Начинать
Во всех примерах асинхронных операций, используемых в промисах в этой статье, используется метод fs.readFile в узле, а на стороне браузера для имитации асинхронных операций можно использовать метод setTimeout.
1. Базовая версия
Цель
- Экземпляры объекта Promise могут быть созданы.
- Если асинхронный метод, переданный экземпляром обещания, выполняется успешно, будет выполнена зарегистрированная функция обратного вызова, а в случае сбоя будет выполнена зарегистрированная функция обратного вызова.
выполнить
function MyPromise(fn) {
let self = this; // 缓存当前promise实例
self.value = null; //成功时的值
self.error = null; //失败时的原因
self.onFulfilled = null; //成功的回调函数
self.onRejected = null; //失败的回调函数
function resolve(value) {
self.value = value;
self.onFulfilled(self.value);//resolve时执行成功回调
}
function reject(error) {
self.error = error;
self.onRejected(self.error)//reject时执行失败回调
}
fn(resolve, reject);
}
MyPromise.prototype.then = function(onFulfilled, onRejected) {
//在这里给promise实例注册成功和失败回调
this.onFulfilled = onFulfilled;
this.onRejected = onRejected;
}
module.exports = MyPromise
Код очень короткий, а логика очень понятная. Затем регистрируются успешные и неудачные обратные вызовы экземпляра промиса. Когда промис ресловится, результат асинхронного выполнения присваивается значению экземпляра промиса, и это значение передается в обратный вызов успеха.Если выполнение не удается, причина отказа асинхронного выполнения назначается ошибке экземпляра обещания, и это значение передается в обратный вызов отказа и выполняется.
Код в этом разделе
2. Поддержка задач синхронизации
Мы знаем, что когда мы используем обещание es6, мы можем передать асинхронную задачу или синхронную задачу, но наша базовая версия кода выше не поддерживает синхронные задачи, Если мы напишем это, будет сообщено об ошибке:
let promise = new Promise((resolve, reject) => {
resolve("同步任务执行")
});
Зачем? Поскольку это синхронная задача, когда наш экземпляр промиса является reslove, его метод then еще не выполнен, поэтому функция обратного вызова еще не зарегистрирована, и в это время успешный обратный вызов в reslove обязательно сообщит об ошибке.
Цель
Обещания поддерживают синхронные методы
выполнить
function resolve(value) {
//利用setTimeout特性将具体执行放到then之后
setTimeout(() => {
self.value = value;
self.onFulfilled(self.value)
})
}
function reject(error) {
setTimeout(() => {
self.error = error;
self.onRejected(self.error)
})
}
Реализация очень простая, то есть обернуть его с помощью setTimeout в reslove и reject, чтобы он выполнялся после выполнения метода then, чтобы мы разрешили промису поддерживать входящие методы синхронизации. Спецификация /A+ также дает понять, что это требуется.
2.2.4 onFulfilled or onRejected must not be called until the execution context stack contains only platform code.
Код в этом разделе
Поддержка синхронного кода задачи
3. Поддержите три состояния
Мы знаем, что при использовании промисов у промисов есть три состояния: ожидание (в процессе), выполнено (успешно) и отклонено (сбой). Только результат асинхронной операции может определить, какое состояние находится в данный момент, и никакая другая операция не может изменить это состояние. Кроме того, если состояние промиса изменилось, то оно больше не изменится. Результат можно получить в любой момент. Состояние объекта обещания меняется. Есть только две возможности: из ожидающего в выполненное и из ожидающего в отклоненное. Пока происходят эти две ситуации, состояние замораживается, и оно больше не изменится. Результат всегда будет поддерживаться. Если изменение уже произошло, если вы добавите callback-функцию в объект промиса, вы получите результат немедленно.
Цель
- Реализуйте три состояния обещания.
- Для реализации изменения состояния объекта-обещания есть только два возможных изменения: с ожидающего на выполненное и с ожидающего на отклоненное.
- Как только состояние обещания изменится, добавление функции обратного вызова к объекту обещания немедленно даст результат.
выполнить
//定义三种状态
const PENDING = "pending";
const FULFILLED = "fulfilled";
const REJECTED = "rejected";
function MyPromise(fn) {
let self = this;
self.value = null;
self.error = null;
self.status = PENDING;
self.onFulfilled = null;
self.onRejected = null;
function resolve(value) {
//如果状态是pending才去修改状态为fulfilled并执行成功逻辑
if (self.status === PENDING) {
setTimeout(function() {
self.status = FULFILLED;
self.value = value;
self.onFulfilled(self.value);
})
}
}
function reject(error) {
//如果状态是pending才去修改状态为rejected并执行失败逻辑
if (self.status === PENDING) {
setTimeout(function() {
self.status = REJECTED;
self.error = error;
self.onRejected(self.error);
})
}
}
fn(resolve, reject);
}
MyPromise.prototype.then = function(onFulfilled, onRejected) {
if (this.status === PENDING) {
this.onFulfilled = onFulfilled;
this.onRejected = onRejected;
} else if (this.status === FULFILLED) {
//如果状态是fulfilled,直接执行成功回调,并将成功值传入
onFulfilled(this.value)
} else {
//如果状态是rejected,直接执行失败回调,并将失败原因传入
onRejected(this.error)
}
return this;
}
module.exports = MyPromise
Сначала мы установили три состояния "ожидание", "выполнено", "отклонено", а затем вынесли суждения в reslove и reject. Только когда состояние находится в ожидании, мы меняем состояние обещания и выполняем соответствующие операции. Кроме того, мы затем рассудите, что если обещание стало «выполненным» или «отклоненным», его обратный вызов выполняется немедленно, и результат передается.
Код в этом разделе
Поддерживаются три кода состояния
4. Поддержка работы цепи
Мы обычно пишем обещания, как правило, соответствующий набор операций процесса, например:
promise.then(f1).then(f2).then(f3)
Но наша предыдущая версия может зарегистрировать не более одного обратного вызова, в этом разделе мы реализуем цепные операции.
Цель
Обещания поддерживают цепочку
выполнить
Если вы хотите поддерживать связанные операции, это на самом деле очень просто: во-первых, вам нужно использовать массив при сохранении обратного вызова.
self.onFulfilledCallbacks = [];
self.onRejectedCallbacks = [];
Конечно, при выполнении обратного вызова его также следует изменить для обхода массива обратного вызова для выполнения функции обратного вызова.
self.onFulfilledCallbacks.forEach((callback) => callback(self.value));
Наконец, следует изменить и метод then.Просто добавьте в последнюю строку return this.Это фактически то же самое, что и принцип работы цепочки jQuery.Каждый раз, когда вызывается метод, он возвращает свой собственный экземпляр, а последний метод также является методом экземпляра, так что вы можете продолжить.
MyPromise.prototype.then = function(onFulfilled, onRejected) {
if (this.status === PENDING) {
this.onFulfilledCallbacks.push(onFulfilled);
this.onRejectedCallbacks.push(onRejected);
} else if (this.status === FULFILLED) {
onFulfilled(this.value)
} else {
onRejected(this.error)
}
return this;
}
Код в этом разделе
Поддержка связанных кодов операций
5. Поддержка последовательных асинхронных задач
Мы реализовали цепные вызовы в предыдущем разделе, но в настоящее время в метод then можно передавать только синхронные задачи, но мы обычно используем промисы, а методы then — это вообще асинхронные задачи, потому что мы используем промисы в основном для решения набора асинхронных задач процесса. , например следующий интерфейс вызова для получения идентификатора пользователя, а затем вызов интерфейса в соответствии с идентификатором пользователя для получения баланса пользователя, и идентификатор пользователя, и баланс пользователя должны вызывать интерфейс, поэтому все они являются асинхронными задачами, как заставить обещание поддерживать последовательную асинхронную операцию?
getUserId()
.then(getUserBalanceById)
.then(function (balance) {
// do sth
}, function (error) {
console.log(error);
});
Цель
Обещания поддерживают последовательные асинхронные операции
выполнить
Здесь мы вводим общий сценарий для удобства объяснения: чтение содержимого файла последовательно с промисами, код сценария выглядит следующим образом:
let p = new Promise((resolve, reject) => {
fs.readFile('../file/1.txt', "utf8", function(err, data) {
err ? reject(err) : resolve(data)
});
});
let f1 = function(data) {
console.log(data)
return new Promise((resolve, reject) => {
fs.readFile('../file/2.txt', "utf8", function(err, data) {
err ? reject(err) : resolve(data)
});
});
}
let f2 = function(data) {
console.log(data)
return new Promise((resolve, reject) => {
fs.readFile('../file/3.txt', "utf8", function(err, data) {
err ? reject(err) : resolve(data)
});
});
}
let f3 = function(data) {
console.log(data);
}
let errorLog = function(error) {
console.log(error)
}
p.then(f1).then(f2).then(f3).catch(errorLog)
//会依次输出
//this is 1.txt
//this is 2.txt
//this is 3.txt
В приведенном выше сценарии мы читаем 1.txt и печатаем содержимое 1.txt, затем читаем 2.txt и печатаем содержимое 2.txt, затем читаем 3.txt и печатаем содержимое 3.txt и читаем файл Все являются асинхронными операциями, поэтому все они возвращают обещание. Обещание, которое мы реализовали в предыдущем разделе, может реализовывать последующие обратные вызовы после выполнения асинхронных операций, но операция обратного вызова в этом разделе для чтения содержимого файла не синхронная, а асинхронная. после чтения 1.txt и выполнения его для обратного вызова f1, f2 и f3 в onFulfilledCallbacks асинхронная операция не была завершена, поэтому мы хотели получить этот вывод:
this is 1.txt
this is 2.txt
this is 3.txt
Но на самом деле выводит
this is 1.txt
this is 1.txt
this is 1.txt
Поэтому, чтобы добиться последовательных асинхронных операций, мы не можем регистрировать функции обратного вызова в onFulfilledCallbacks исходного обещания, а регистрируем каждую функцию обратного вызова в onFulfilledCallbacks соответствующего обещания асинхронной операции.В качестве примера возьмем сценарий чтения файла, f1 должен быть в onFulfilledCallbacks p, а f2 должен быть в onFulfilledCallbacks промиса, возвращенного в f1, потому что только таким образом можно распечатать результат 2.txt после чтения 2.txt.
Однако обычно мы пишем обещания следующим образом:promise.then(f1).then(f2).then(f3)
, Мы указали все процессы в начале, вместо того, чтобы прописать обратный вызов f1 в f1, а обратный вызов f2 прописать в f2.
Как сохранить этот цепной метод записи и сделать так, чтобы асинхронные операции выполнялись непрерывно? На самом деле мы позволяем методу then не возвращать собственный экземпляр, а возвращать новое обещание. Мы можем назвать его bridgePromise. Его самая большая функция — соединение последующих операций. Давайте посмотрим на конкретный код реализации:
MyPromise.prototype.then = function(onFulfilled, onRejected) {
const self = this;
let bridgePromise;
//防止使用者不传成功或失败回调函数,所以成功失败回调都给了默认回调函数
onFulfilled = typeof onFulfilled === "function" ? onFulfilled : value => value;
onRejected = typeof onRejected === "function" ? onRejected : error => { throw error };
if (self.status === FULFILLED) {
return bridgePromise = new MyPromise((resolve, reject) => {
setTimeout(() => {
try {
let x = onFulfilled(self.value);
resolvePromise(bridgePromise, x, resolve, reject);
} catch (e) {
reject(e);
}
});
})
}
if (self.status === REJECTED) {
return bridgePromise = new MyPromise((resolve, reject) => {
setTimeout(() => {
try {
let x = onRejected(self.error);
resolvePromise(bridgePromise, x, resolve, reject);
} catch (e) {
reject(e);
}
});
});
}
if (self.status === PENDING) {
return bridgePromise = new MyPromise((resolve, reject) => {
self.onFulfilledCallbacks.push((value) => {
try {
let x = onFulfilled(value);
resolvePromise(bridgePromise, x, resolve, reject);
} catch (e) {
reject(e);
}
});
self.onRejectedCallbacks.push((error) => {
try {
let x = onRejected(error);
resolvePromise(bridgePromise, x, resolve, reject);
} catch (e) {
reject(e);
}
});
});
}
}
//catch方法其实是个语法糖,就是只传onRejected不传onFulfilled的then方法
MyPromise.prototype.catch = function(onRejected) {
return this.then(null, onRejected);
}
//用来解析回调函数的返回值x,x可能是普通值也可能是个promise对象
function resolvePromise(bridgePromise, x, resolve, reject) {
//如果x是一个promise
if (x instanceof MyPromise) {
//如果这个promise是pending状态,就在它的then方法里继续执行resolvePromise解析它的结果,直到返回值不是一个pending状态的promise为止
if (x.status === PENDING) {
x.then(y => {
resolvePromise(bridgePromise, y, resolve, reject);
}, error => {
reject(error);
});
} else {
x.then(resolve, reject);
}
//如果x是一个普通值,就让bridgePromise的状态fulfilled,并把这个值传递下去
} else {
resolve(x);
}
}
Во-первых, чтобы пользователь не передал функцию обратного вызова при успешном выполнении или функцию обратного вызова при ошибке, мы даем функцию обратного вызова по умолчанию, а затем, независимо от текущего обещания, мы возвращаем мостПромис для подключения последующих операций.
Кроме того, при выполнении функции обратного вызова, поскольку функция обратного вызова может возвращать асинхронное обещание или синхронный результат, мы напрямую размещаем результат функции обратного вызова в bridgePromise и используем метод resolvePromise для разрешения результата функции обратного вызова. Функция возвращает обещание, а статус все еще находится в состоянии ожидания. Продолжайте анализировать значение, переданное промисом reslove в методе then обещания. Если значение все еще является обещанием в состоянии ожидания, продолжайте анализировать, пока оно не перестанет быть асинхронным обещание, но нормальное значение.Используйте метод reslove в bridgePromise, чтобы изменить статус bridgePromise на выполнено, и вызовите метод в массиве обратного вызова onFulfilledCallbacks, чтобы передать значение, и асинхронная операция будет подключена.
Это очень абстрактно, давайте нарисуем картинку для пояснения процесса в сцене чтения в порядке файлов:
при исполненииp.then(f1).then(f2).then(f3)
Время:
- Сначала выполните p.then(f1), чтобы вернуть bridgePromise (p2), и поместите функцию обратного вызова в список обратных вызовов onFulfilledCallbacks p, функция обратного вызова отвечает за выполнение f1 и обновление состояния p2.
- Тогда .then(f2) возвращает bridgePromise(p3).Обратите внимание, что на самом деле это p2.then(f2), потому что p.then(f1) возвращает p2. В этот момент функция обратного вызова помещается в список обратных вызовов onFulfilledCallbacks p2, и функция обратного вызова отвечает за выполнение f2 и обновление состояния p3.
- Затем .then(f3) возвращает bridgePromise (p4) и помещает функцию обратного вызова в список обратных вызовов onFulfilledCallbacks p3.Функция обратного вызова отвечает за выполнение f3 и обновление состояния p4. В этот момент регистрируется отношение обратного вызова, как показано на рисунке:
- Затем через некоторое время асинхронная операция в p завершается, содержимое 1.txt считывается и выполняется функция обратного вызова p, функция обратного вызова выполняет f1, а содержимое 1.txt "это 1 .txt» и поместите возвращаемое значение f1 в resolvePromise, чтобы начать синтаксический анализ. Когда resolvePromise видит, что передан объект обещания, обещание является асинхронным, поэтому мне приходится ждать, поэтому я продолжаю разрешать промис в методе then объекта обещания.Результат разрешения объекта обещания не является объектом обещания. , но конкретный. Значение «это 2.txt», поэтому метод reslove для bridgePromise(p2) вызывается для обновления состояния bridgePromise(p2) до выполненного, и передается «это 2.txt». в функцию обратного вызова p2 для выполнения.
- Обратный вызов p2 начинает выполняться, f2 получает переданный параметр «это 2.txt» и начинает выполняться, распечатывает содержимое 2.txt и помещает возвращаемое значение f2 в resolvePromise для начала разбора, resolvePromise видит incoming Объект обещания, обещание является асинхронным, и оно должно ждать... Последующая операция заключается в повторении шагов 4 и 5 до конца.
На данный момент строка reslove пройдена, давайте посмотрим на строку reject, с reject на самом деле очень просто справиться:
- Во-первых, при выполнении fn и выполнении зарегистрированного обратного вызова он оборачивается с помощью try-catch, где бы ни было исключение, оно попадет в ветку reject.
- Как только код входит в ветку отклонения, напрямую установите промис моста в состояние отклонено, поэтому ветка будет отклонена в будущем.Кроме того, если функция onRejected для обработки исключений не передана, по умолчанию используется ошибка броска выбросить ошибку полностью назад, чтобы ошибка всплыла вверх.
- Наконец, можно реализовать функцию catch для получения ошибок.
MyPromise.prototype.catch = function(onRejected) {
return this.then(null, onRejected);
}
На данный момент мы можем использовать его с удовольствиемpromise.then(f1).then(f2).then(f3).catch(errorLog)
для последовательного чтения содержимого файла.
Код в этом разделе
Поддержка последовательного асинхронного кода задачи
6. Соответствовать спецификации Promises/A+
На самом деле, в разделе о поддержке последовательных асинхронных задач обещания, которые мы написали, в основном завершены по функциям, но они не стандартизированы, например, суждения в других ситуациях и т. д. В этом разделе мы сравниваем обещания/A+. Спецификация полирует обещание, которое мы написали. Если вы просто хотите изучить основную реализацию промисов, не имеет значения, если вы не понимаете этот раздел, потому что этот раздел не расширяет функции промисов, а только делает промисы более стандартизированными и надежными.
Цель
Дайте обещания спецификации Promises/A+ и пройдите полные тесты promises-aplus-tests
выполнить
Во-первых, давайте взглянем на спецификацию Promises/A+:
Обещания/A+ Спецификация Оригинал
Спецификация Promises/A+ на китайском языке
По сравнению с кодом в предыдущем разделе код в этом разделе не изменился, за исключением того, что в функцию resolvePromise было добавлено несколько других решений. Полный код обещания выглядит следующим образом:
const PENDING = "pending";
const FULFILLED = "fulfilled";
const REJECTED = "rejected";
function MyPromise(fn) {
const self = this;
self.value = null;
self.error = null;
self.status = PENDING;
self.onFulfilledCallbacks = [];
self.onRejectedCallbacks = [];
function resolve(value) {
if (value instanceof MyPromise) {
return value.then(resolve, reject);
}
if (self.status === PENDING) {
setTimeout(() => {
self.status = FULFILLED;
self.value = value;
self.onFulfilledCallbacks.forEach((callback) => callback(self.value));
}, 0)
}
}
function reject(error) {
if (self.status === PENDING) {
setTimeout(function() {
self.status = REJECTED;
self.error = error;
self.onRejectedCallbacks.forEach((callback) => callback(self.error));
}, 0)
}
}
try {
fn(resolve, reject);
} catch (e) {
reject(e);
}
}
function resolvePromise(bridgepromise, x, resolve, reject) {
//2.3.1规范,避免循环引用
if (bridgepromise === x) {
return reject(new TypeError('Circular reference'));
}
let called = false;
//这个判断分支其实已经可以删除,用下面那个分支代替,因为promise也是一个thenable对象
if (x instanceof MyPromise) {
if (x.status === PENDING) {
x.then(y => {
resolvePromise(bridgepromise, y, resolve, reject);
}, error => {
reject(error);
});
} else {
x.then(resolve, reject);
}
// 2.3.3规范,如果 x 为对象或者函数
} else if (x != null && ((typeof x === 'object') || (typeof x === 'function'))) {
try {
// 是否是thenable对象(具有then方法的对象/函数)
//2.3.3.1 将 then 赋为 x.then
let then = x.then;
if (typeof then === 'function') {
//2.3.3.3 如果 then 是一个函数,以x为this调用then函数,且第一个参数是resolvePromise,第二个参数是rejectPromise
then.call(x, y => {
if (called) return;
called = true;
resolvePromise(bridgepromise, y, resolve, reject);
}, error => {
if (called) return;
called = true;
reject(error);
})
} else {
//2.3.3.4 如果 then不是一个函数,则 以x为值fulfill promise。
resolve(x);
}
} catch (e) {
//2.3.3.2 如果在取x.then值时抛出了异常,则以这个异常做为原因将promise拒绝。
if (called) return;
called = true;
reject(e);
}
} else {
resolve(x);
}
}
MyPromise.prototype.then = function(onFulfilled, onRejected) {
const self = this;
let bridgePromise;
onFulfilled = typeof onFulfilled === "function" ? onFulfilled : value => value;
onRejected = typeof onRejected === "function" ? onRejected : error => { throw error };
if (self.status === FULFILLED) {
return bridgePromise = new MyPromise((resolve, reject) => {
setTimeout(() => {
try {
let x = onFulfilled(self.value);
resolvePromise(bridgePromise, x, resolve, reject);
} catch (e) {
reject(e);
}
}, 0);
})
}
if (self.status === REJECTED) {
return bridgePromise = new MyPromise((resolve, reject) => {
setTimeout(() => {
try {
let x = onRejected(self.error);
resolvePromise(bridgePromise, x, resolve, reject);
} catch (e) {
reject(e);
}
}, 0);
});
}
if (self.status === PENDING) {
return bridgePromise = new MyPromise((resolve, reject) => {
self.onFulfilledCallbacks.push((value) => {
try {
let x = onFulfilled(value);
resolvePromise(bridgePromise, x, resolve, reject);
} catch (e) {
reject(e);
}
});
self.onRejectedCallbacks.push((error) => {
try {
let x = onRejected(error);
resolvePromise(bridgePromise, x, resolve, reject);
} catch (e) {
reject(e);
}
});
});
}
}
MyPromise.prototype.catch = function(onRejected) {
return this.then(null, onRejected);
}
// 执行测试用例需要用到的代码
MyPromise.deferred = function() {
let defer = {};
defer.promise = new MyPromise((resolve, reject) => {
defer.resolve = resolve;
defer.reject = reject;
});
return defer;
}
try {
module.exports = MyPromise
} catch (e) {}
Мы можем сначала запустить тест, установить тестовый плагин, а затем выполнить тест.При тестировании обратите внимание на добавление последних нескольких строк кода выше для выполнения тестового примера.
1.npm i -g promises-aplus-tests
2.promises-aplus-tests mypromise.js
Запустив тестовый пример, вы увидите, что код обещания, который мы написали выше, прошел полный тест спецификации Promises/A+.
Затем мы начали анализировать код в этом разделе. В основном мы добавили два дополнительных суждения к resolvePromise. Первое заключается в том, что когда x и bridgePromise указывают на одно и то же значение, сообщается об ошибке циклической ссылки, поэтому обещание соответствует 2.3. Спецификация 1. Затем мы добавили суждение о том, что x является объектом или функцией. Это суждение в основном соответствует спецификации 2.3.3. Китайская спецификация показана на рисунке:
Этот стандарт соответствует объекту, который может быть реализован.Что такое объект, который можно использовать, пока существует метод then, это объект, который можно реализовать, и тогда мы можем реализовать его в соответствии со спецификацией, когда мы его реализуем.else if (x != null && ((typeof x === 'object') || (typeof x === 'function'))) {
try {
// 是否是thenable对象(具有then方法的对象/函数)
//2.3.3.1 将 then 赋为 x.then
let then = x.then;
if (typeof then === 'function') {
//2.3.3.3 如果 then 是一个函数,以x为this调用then函数,且第一个参数是resolvePromise,第二个参数是rejectPromise
then.call(x, y => {
if (called) return;
called = true;
resolvePromise(bridgepromise, y, resolve, reject);
}, error => {
if (called) return;
called = true;
reject(error);
})
} else {
//2.3.3.4 如果 then不是一个函数,则以x为值fulfill promise。
resolve(x);
}
} catch (e) {
//2.3.3.2 如果在取x.then值时抛出了异常,则以这个异常做为原因将promise拒绝。
if (called) return;
called = true;
reject(e);
}
}
После написания кода этой ветки мы можем ее собственно удалитьif (x instanceof MyPromise) {}
Код этой ветки, поскольку обещание также является доступным объектом, может быть полностью заменен вышеприведенным кодом. Кроме того, много повторяющегося кода в этом разделе можно инкапсулировать и оптимизировать, но чтобы было ясно видно, абстрактной инкапсуляции нет.Если вы считаете, что повторяющихся кодов слишком много, вы можете абстрагировать и инкапсулировать их самостоятельно.
Код в этом разделе
Достижение обещаний/код спецификации A+
7. Внедряйте все, соревнуйтесь, решайтесь, отказывайтесь от методов обещания
В предыдущем разделе мы реализовали промис, соответствующий спецификации Promises/A+, а в этом разделе мы реализуем некоторые общие методы в промисах es6.
Цель
Реализовать все, состязаться, разрешать, отклонять методы обещаний es6
выполнить
Продолжаем писать на основе предыдущего:
MyPromise.all = function(promises) {
return new MyPromise(function(resolve, reject) {
let result = [];
let count = 0;
for (let i = 0; i < promises.length; i++) {
promises[i].then(function(data) {
result[i] = data;
if (++count == promises.length) {
resolve(result);
}
}, function(error) {
reject(error);
});
}
});
}
MyPromise.race = function(promises) {
return new MyPromise(function(resolve, reject) {
for (let i = 0; i < promises.length; i++) {
promises[i].then(function(data) {
resolve(data);
}, function(error) {
reject(error);
});
}
});
}
MyPromise.resolve = function(value) {
return new MyPromise(resolve => {
resolve(value);
});
}
MyPromise.reject = function(error) {
return new MyPromise((resolve, reject) => {
reject(error);
});
}
На самом деле, после реализации основной логики промиса в предыдущих разделах, эти методы реализовать не сложно, принцип всего - вернуть промис, а в методе then всех пришедших промисов прописать колбэк . Обратный вызов выполнен успешно. Поместите значение в массив результатов, пусть возвращенный промис перейдет к reslove, когда все обратные вызовы будут успешными, и верните массив результатов. Гонка аналогична всем, за исключением того, что она не будет ждать, пока все промисы будут выполнены. получится, но кто быстро, кто вернется, логика решения и отклонения тоже очень проста, просто посмотрите на нее и поймете.
Код в этом разделе
Реализовать все, состязаться, разрешать, отклонять код метода
8. Реализуйте метод promiseify
На самом деле, пока предыдущий раздел не завершился с методом promise, в этом разделе рассказывается о методе promiseify в известной библиотеке обещаний bluebird, потому что этот метод очень распространен и о нем спрашивали в предыдущих интервью. Что делает обещание? Его функция состоит в том, чтобы преобразовать api функции асинхронного обратного вызова в форму промиса, например следующую, после выполнения promiseify для fs.readFile вы можете напрямую использовать метод промиса для вызова метода чтения файла, это очень мощно?
let Promise = require('./bluebird');
let fs = require("fs");
var readFile = Promise.promisify(fs.readFile);
readFile("1.txt", "utf8").then(function(data) {
console.log(data);
})
Цель
Реализовать метод promiseify bluebird
выполнить
MyPromise.promisify = function(fn) {
return function() {
var args = Array.from(arguments);
return new MyPromise(function(resolve, reject) {
fn.apply(null, args.concat(function(err) {
err ? reject(err) : resolve(arguments[1])
}));
})
}
}
Несмотря на то, что метод очень мощный, реализовать его несложно. Если вы хотите вызвать метод promise напрямую извне, то верните обещание. Внутри исходный параметр сращивается с параметром callback-функции, а reslove метод этого обещания выполняется в функции обратного вызова, результат передается, и обещание реализуется.
Код в этом разделе
Наконец
Я написал так много, не зная об этом. Если вы считаете, что это нормально, пожалуйста, поставьте лайк. Кроме того, код каждого раздела размещен на github. Вы можете проверить код реализации обещания и тестовый код этого раздела, и прошу помощи кстати. звезда~
адрес проекта:Репозиторий кода этой статьи на github
Кроме того, реализация промиса, соответствующего спецификации Promises/A+, является не только одним из методов реализации в этой статье.В этой статье в качестве объяснения выбран относительно простой для понимания метод реализации.Вы также можете реализовать промис, соответствующий спецификации спецификацию Promises/A+ по-своему.