предисловие
В качестве решения для асинхронного программирования Promise более эффективен, чем традиционные обратные вызовы и события, а также необходимо изучить внешний интерфейс. Как начинающий фронтенд, вы должны не только освоить использование промисов, но и иметь определенное представление о принципах их реализации (грубо говоря, это необходимо для того, чтобы интервью притворялись принудительными). Хотя в Интернете есть много кодов реализации Promise, с сотнями строк, лично я чувствую, что если у меня нет определенного понимания асинхронного программирования и Promise, эти коды - просто доска (вы не можете постучать по ней). доска для собеседования). Во-первых, читатели по умолчанию знакомы с объектами Promise, а затем с точки зрения наиболее часто используемых шаблонов проектирования интерфейса: шаблонов публикации-подписки и наблюдателя, Promise будет реализован шаг за шагом.
Начните с асинхронного программирования
Поскольку Promise — асинхронное решение, как выполняется асинхронная обработка до того, как объект Promise перестанет существовать? Существует два подхода: функции обратного вызова и шаблон проектирования «публикация-подписка» или «наблюдатель». (Дополнительные способы реализации асинхронного программирования см. в моей статье:5 способов реализовать асинхронное программирование в JavaScript)
функция обратного вызова
Я считаю, что callback-функция знакома читателям, ведь самый ранний контакт — это callback-функция, а также очень просто использовать callback-функцию для асинхронной обработки. файл обычно делает это.
fs.readFile("h.js", (err, data) => {
console.log(data.toString())
});
Его недостатки также очевидны: когда асинхронный процесс усложняется, обратный вызов также становится очень сложным, иногда его называют «ад обратных вызовов» на примере копирования файлов.
fs.exists("h.js", exists => { // 文件是否存在
if (exists) {
fs.readFile("h.js", (err, data) => { // 读文件
fs.mkdir(__dirname + "/js/", err => { // 创建目录
fs.writeFile(__dirname + "/js/h.js", data, err => { // 写文件
console.log("复制成功,再回调下去,代码真的很难看得懂")
})
});
});
}
});
На самом деле код все еще можно прочитать, спасибо JS-дизайнеру за то, что он не убрал фигурные скобки функции. Написание обратных вызовов наподобие python без фигурных скобок — это (просто шутка. Не сказать, что python — это плохо, в конце концов, JavaScript — лучший язык в мире)
# 这代码属实没法看啊
def callback_1():
# processing ...
def callback_2():
# processing.....
def callback_3():
# processing ....
def callback_4():
#processing .....
def callback_5():
# processing ......
async_function1(callback_5)
async_function2(callback_4)
async_function3(callback_3)
async_function4(callback_2)
async_function5(callback_1)
Шаблоны проектирования "публикация-подписка" и "наблюдатель"
Впервые я познакомился с шаблонами проектирования, когда изучал Java и C++, ведь шаблоны проектирования предлагались на основе объектно-ориентированного разделения объектов. Шаблон проектирования «публикация-подписка» похож на шаблон «наблюдатель», но есть небольшие отличия (тестовый сайт интервью находится здесь).
Шаблон наблюдателяВ разработке программного обеспечения это объект, который поддерживает список зависимостей и автоматически уведомляет их при изменении любого состояния.
Опубликовать модель подпискирежим передачи сообщений,диктор(Издатели)Как правило, сообщения публикуются в определенном центре сообщений,подписчик(Подписчик)Вы можете подписаться на информацию из центра сообщений в соответствии с вашими потребностями, что очень похоже на очередь сообщений..
В шаблоне наблюдателя есть только два компонента: получатель и издатель, а в шаблоне публикация-подписка — три компонента: издатель, центр сообщений и получатель.
Разница в реализации кода также более очевидна.
Шаблон проектирования наблюдателя
// 观察者设计模式
class Observer {
constructor () {
this.observerList = [];
}
subscribe (observer) {
this.observerList.push(observer)
}
notifyAll (value) {
this.observerList.forEach(observe => observe(value))
}
}
Шаблон проектирования публикации-подписки (nodejs EventEmitter)
// 发布订阅
class EventEmitter {
constructor () {
this.eventChannel = {}; // 消息中心
}
// subscribe
on (event, callback) {
this.eventChannel[event] ? this.eventChannel[event].push(callback) : this.eventChannel[event] = [callback]
}
// publish
emit (event, ...args) {
this.eventChannel[event] && this.eventChannel[event].forEach(callback => callback(...args))
}
// remove event
remove (event) {
if (this.eventChannel[event]) {
delete this.eventChannel[event]
}
}
// once event
once (event, callback) {
this.on(event, (...args) => {
callback(...args);
this.remove(event)
})
}
}
Разницу между ними видно и из кода.Режим наблюдателя не классифицирует события.При возникновении события все наблюдатели будут уведомлены. Шаблон проектирования «публикация-подписка» классифицирует события и инициирует разные события, которые будут уведомлять разных наблюдателей. Следовательно, можно считать, что последний является модернизированной версией первого с более детальным разделением событий уведомления.
Применение публикации-подписки и наблюдателей в асинхронном режиме
// 观察者
const observer = new Observer();
observer.subscribe(value => {
console.log("第一个观察者,接收到的值为:");
console.log(value)
});
observer.subscribe(value => {
console.log("第二个观察者,接收到的值为");
console.log(value)
});
fs.readFile("h.js", (err, data) => {
observer.notifyAll(data.toString())
});
// 发布-订阅
const event = new EventEmitter();
event.on("err", console.log);
event.on("data", data => {
// do something
console.log(data)
});
fs.readFile("h.js", (err, data) => {
if (err) event.emit("err", err);
event.emit("data", data.toString())
});
В асинхронном программировании оба шаблона проектирования реализуют асинхронное программирование путем регистрации глобальных наблюдателей или глобальных событий с последующим уведомлением всех наблюдателей или инициированием определенных событий в асинхронной среде.
Недостатки также очевидны, например, слишком много глобальных наблюдателей/событий, которые трудно поддерживать, конфликты имен событий и т. д. Так родился Promise.
Анализировать и реализовывать промисы с точки зрения шаблона проектирования наблюдателя.
Promise в определенной степени наследует идею паттерна проектирования наблюдателя и публикации-подписки.Давайте начнем с фрагмента кода Promise, чтобы проанализировать, как Promise использует паттерн проектирования наблюдателя.
const asyncReadFile = filename => new Promise((resolve) => {
fs.readFile(filename, (err, data) => {
resolve(data.toString()); // 发布者 相当于观察者模式的notifyAll(value) 或者发布订阅模式的emit
});
});
asyncReadFile("h.js").then(value => { // 订阅者 相当于观察者模式的subscribe(value => console.log(value)) 或者发布订阅模式的on
console.log(value);
});
Из приведенного выше кода Promise я думаю, что причина, по которой схема Promise лучше, чем предыдущая схема публикации-подписки/наблюдателя, заключается в следующем: инкапсуляция асинхронных задач, издатель событий находится в функции обратного вызова (разрешение), а приемник событий в объектном методе (then()), используя локальные события, лучше инкапсулирует оба, а не бросает их в глобальные.
Обещание реализации
Основываясь на приведенных выше идеях, мы можем реализовать простое обещание: MyPromise
class MyPromise {
constructor (run) { // run 函数 (resolve) => any
this.observerList = [];
const notifyAll = value => this.observerList.forEach(callback => callback(value));
run(notifyAll); // !!! 核心
}
subscribe (callback) {
this.observerList.push(callback);
}
}
//
const p = new MyPromise(notifyAll => {
fs.readFile("h.js", (err, data) => {
notifyAll(data.toString()) // resolve
})
});
p.subscribe(data => console.log(data)); // then
Несколько строк кода для реализации простого промиса, и приведенный выше код представляет собой изменение шаблона проектирования наблюдателя лишь незначительно.
добавить состояние
Конечно, это еще не конец, MyPromise выше проблематичен. Как упоминалось ранее, промис — это инкапсуляция асинхронных задач, которую можно рассматривать как наименьшую асинхронную единицу (подобную обратному вызову), и должен быть только один асинхронный результат, то есть разрешение в промисе можно использовать только один раз, что эквивалентно одноразовому событию EventEmitter. NotifyAll из MyPromise, реализованный выше, можно использовать несколько раз (почему-то нет), поэтому это может привести к более чем одной ошибке в результате асинхронных задач. Поэтому решение состоит в том, чтобы добавить логическую переменную или добавить состояния, которые являются ожидающими и выполненными состояниями (по сути, то же самое, что и логическая переменная).Когда notifyAll вызывается один раз, немедленно блокируйте notifyAll или снова вызывайте notifyAll, когда ожидающее состояние изменяется на выполненное состояние. функция не будет работать.
Для согласования с объектом Promise здесь используется метод добавления состояния (кстати, название метода изменено, notifyAll => resolve, subscribe => then).
const pending = "pending";
const fulfilled = "fulfilled";
class MyPromise {
constructor (run) { // run 函数 (resolve) => any
this.observerList = [];
this.status = pending;
const resolve = value => {
if (this.status === pending) {
this.status = fulfilled;
this.observerList.forEach(callback => callback(value));
}
};
run(resolve); // !!! 核心
}
then (callback) {
this.observerList.push(callback);
}
}
const p = new MyPromise(resolve => {
setTimeout(() => {
resolve("hello world");
resolve("hello world2"); // 不好使了
}, 1000);
});
p.then(value => console.log(value));
Реализовать связанные вызовы
Кажется, что это начинает немного набрасываться, но then в MyPromise не имеет цепочек вызовов.Далее давайте реализуем then-цепочку.Следует отметить, что then-метод в Promise возвращает новый экземпляр Promise, а не предыдущий Обещать. Поскольку метод then продолжает возвращать новые объекты MyPromise, необходимо свойство для хранения уникального асинхронного результата. С другой стороны, реализация метода then по-прежнему должна регистрировать обратный вызов, но реализация должна учитывать текущее состояние.Если он находится в состоянии ожидания, нам нужно зарегистрировать обратный вызов в очереди при возврате нового MyPromise. , Если он находится в состоянии выполнения, то верните новый объект MyPromise напрямую и передайте результат предыдущего объекта MyPromise новому объекту MyPromise.
const pending = "pending";
const fulfilled = "fulfilled";
class MyPromise {
constructor (run) { // run 函数 (resolve) => any
this.resolvedCallback = [];
this.status = pending;
this.data = void 666; // 保存异步结果
const resolve = value => {
if (this.status === pending) {
this.status = fulfilled;
this.data = value; // 存一下结果
this.resolvedCallback.forEach(callback => callback(this.data));
}
};
run(resolve); // !!! 核心
}
then (onResolved) {
// 这里需要对onResolved做一下处理,当onResolved不是函数时将它变成函数
onResolved = typeof onResolved === "function" ? onResolved : value => value;
switch (this.status) {
case pending: {
return new MyPromise(resolve => {
this.resolvedCallback.push(value => { // 再包装
const result = onResolved(value); // 需要判断一下then接的回调返回的是不是一个MyPromise对象
if (result instanceof MyPromise) {
result.then(resolve) // 如果是,直接使用result.then后的结果,毕竟Promise里面就需要这么做
} else {
resolve(result); // 感受一下闭包的伟大
}
})
})
}
case fulfilled: {
return new MyPromise(resolve => {
const result = onResolved(this.data); // fulfilled态,this.data一定存在,其实这里就像map过程
if (result instanceof MyPromise) {
result.then(resolve)
} else {
resolve(result); // 闭包真伟大
}
})
}
}
}
}
const p = new MyPromise(resolve => {
setTimeout(() => {
resolve("hello world");
resolve("hello world2"); // 不好使了
}, 1000);
});
p.then(value => value + "dpf")
.then(value => value.toUpperCase())
.then(console.log);
Приведенный выше код требует ключевого понимания, в конце концов, понять вышеуказанный код, он легко ниже.
обработка ошибок
Выполнены только объекты разрешения, а затем объекты MyPromise. Библиотеки без тестов — хулиганы, а код без обработки ошибок — хулиганы, поэтому обработка ошибок по-прежнему очень важна. Так как асинхронная задача может не завершиться или в середине может возникнуть ошибка, эту ситуацию необходимо обработать. Поэтому нам нужно добавить состояние reject, чтобы указать на ошибку в асинхронной задаче, и использовать очередь rejectCallback для хранения события ошибки, отправленного reject. (Предупреждение о высокой энергии впереди, программирование, ориентированное на попытку/улов, началось)
const pending = "pending";
const fulfilled = "fulfilled";
const rejected = "rejected"; // 添加状态 rejected
class MyPromise {
constructor (run) { // run 函数 (resolve, reject) => any
this.resolvedCallback = [];
this.rejectedCallback = []; // 添加一个处理错误的队列
this.status = pending;
this.data = void 666; // 保存异步结果
const resolve = value => {
if (this.status === pending) {
this.status = fulfilled;
this.data = value;
this.resolvedCallback.forEach(callback => callback(this.data));
}
};
const reject = err => {
if (this.status === pending) {
this.status = rejected;
this.data = err;
this.rejectedCallback.forEach(callback => callback(this.data));
}
};
try { // 对构造器里传入的函数进行try / catch
run(resolve, reject); // !!! 核心
} catch (e) {
reject(e)
}
}
then (onResolved, onRejected) { // 添加两个监听函数
// 这里需要对onResolved做一下处理,当onResolved不是函数时将它变成函数
onResolved = typeof onResolved === "function" ? onResolved : value => value;
onRejected = typeof onRejected === "function" ? onRejected : err => { throw err };
switch (this.status) {
case pending: {
return new MyPromise((resolve, reject) => {
this.resolvedCallback.push(value => {
try { // 对整个onResolved进行try / catch
const result = onResolved(value);
if (result instanceof MyPromise) {
result.then(resolve, reject)
} else {
resolve(result);
}
} catch (e) {
reject(e) // 捕获异常,将异常发布
}
});
this.rejectedCallback.push(err => {
try { // 对整个onRejected进行try / catch
const result = onRejected(err);
if (result instanceof MyPromise) {
result.then(resolve, reject)
} else {
reject(err)
}
} catch (e) {
reject(err) // 捕获异常,将异常发布
}
})
})
}
case fulfilled: {
return new MyPromise((resolve, reject) => {
try { // 对整个过程进行try / catch
const result = onResolved(this.data);
if (result instanceof MyPromise) {
result.then(resolve, reject)
} else {
resolve(result);
}
} catch (e) {
reject(e) // 捕获异常,将异常发布
}
})
}
case rejected: {
return new MyPromise((resolve, reject) => {
try { // 对整个过程进行try / catch
const result = onRejected(this.data);
if (result instanceof MyPromise) {
result.then(resolve, reject)
} else {
reject(result)
}
} catch (e) {
reject(e) // 捕获异常,将异常发布
}
})
}
}
}
}
const p = new MyPromise((resolve, reject) => {
setTimeout(() => {
reject(new Error("error"));
resolve("hello world"); // 不好使了
resolve("hello world2"); // 不好使了
}, 1000);
});
p.then(value => value + "dpf")
.then(console.log)
.then(() => {}, err => console.log(err));
Видно, что реализация метода then более сложная, но это основной метод, после него легко реализовать остальные методы Реализация каждого метода MyPromise приведена ниже.
поймать реализацию
Эта реализация очень проста
catch (onRejected) {
return this.then(void 666, onRejected)
}
Статический метод MyPromise.resolve
static resolve(p) {
if (p instanceof MyPromise) {
return p.then()
}
return new MyPromise((resolve, reject) => {
resolve(p)
})
}
Статический метод MyPromise.reject
static reject(p) {
if (p instanceof MyPromise) {
return p.catch()
}
return new MyPromise((resolve, reject) => {
reject(p)
})
}
Статический метод MyPromise.all
static all (promises) {
return new MyPromise((resolve, reject) => {
try {
let count = 0,
len = promises.length,
value = [];
for (let promise of promises) {
MyPromise.resolve(promise).then(v => {
count ++;
value.push(v);
if (count === len) {
resolve(value)
}
})
}
} catch (e) {
reject(e)
}
});
}
Статический метод MyPromise.race
static race(promises) {
return new MyPromise((resolve, reject) => {
try {
for (let promise of promises) {
MyPromise.resolve(promise).then(resolve)
}
} catch (e) {
reject(e)
}
})
}
Полная реализация кода MyPromise
const pending = "pending";
const fulfilled = "fulfilled";
const rejected = "rejected"; // 添加状态 rejected
class MyPromise {
constructor (run) { // run 函数 (resolve, reject) => any
this.resolvedCallback = [];
this.rejectedCallback = []; // 添加一个处理错误的队列
this.status = pending;
this.data = void 666; // 保存异步结果
const resolve = value => {
if (this.status === pending) {
this.status = fulfilled;
this.data = value;
this.resolvedCallback.forEach(callback => callback(this.data));
}
};
const reject = err => {
if (this.status === pending) {
this.status = rejected;
this.data = err;
this.rejectedCallback.forEach(callback => callback(this.data));
}
};
try { // 对构造器里传入的函数进行try / catch
run(resolve, reject); // !!! 核心
} catch (e) {
reject(e)
}
}
static resolve (p) {
if (p instanceof MyPromise) {
return p.then()
}
return new MyPromise((resolve, reject) => {
resolve(p)
})
}
static reject (p) {
if (p instanceof MyPromise) {
return p.catch()
}
return new MyPromise((resolve, reject) => {
reject(p)
})
}
static all (promises) {
return new MyPromise((resolve, reject) => {
try {
let count = 0,
len = promises.length,
value = [];
for (let promise of promises) {
MyPromise.resolve(promise).then(v => {
count ++;
value.push(v);
if (count === len) {
resolve(value)
}
})
}
} catch (e) {
reject(e)
}
});
}
static race(promises) {
return new MyPromise((resolve, reject) => {
try {
for (let promise of promises) {
MyPromise.resolve(promise).then(resolve)
}
} catch (e) {
reject(e)
}
})
}
catch (onRejected) {
return this.then(void 666, onRejected)
}
then (onResolved, onRejected) { // 添加两个监听函数
// 这里需要对onResolved做一下处理,当onResolved不是函数时将它变成函数
onResolved = typeof onResolved === "function" ? onResolved : value => value;
onRejected = typeof onRejected === "function" ? onRejected : err => { throw err };
switch (this.status) {
case pending: {
return new MyPromise((resolve, reject) => {
this.resolvedCallback.push(value => {
try { // 对整个onResolved进行try / catch
const result = onResolved(value);
if (result instanceof MyPromise) {
result.then(resolve, reject)
} else {
resolve(result);
}
} catch (e) {
reject(e)
}
});
this.rejectedCallback.push(err => {
try { // 对整个onRejected进行try / catch
const result = onRejected(err);
if (result instanceof MyPromise) {
result.then(resolve, reject)
} else {
reject(err)
}
} catch (e) {
reject(err)
}
})
})
}
case fulfilled: {
return new MyPromise((resolve, reject) => {
try { // 对整个过程进行try / catch
const result = onResolved(this.data);
if (result instanceof MyPromise) {
result.then(resolve, reject)
} else {
resolve(result); // emit
}
} catch (e) {
reject(e)
}
})
}
case rejected: {
return new MyPromise((resolve, reject) => {
try { // 对整个过程进行try / catch
const result = onRejected(this.data);
if (result instanceof MyPromise) {
result.then(resolve, reject)
} else {
reject(result)
}
} catch (e) {
reject(e)
}
})
}
}
}
}
Суммировать
В этой статье мы хотим проанализировать реализацию Promise на основе шаблонов публикации-подписки и наблюдателя, начиная с эволюции асинхронного программирования, функций обратного вызова для публикации-подписки и шаблонов проектирования наблюдателя, а затем обнаружить, что шаблоны проектирования Promises и наблюдателя схожи, поэтому Сначала с этой точки зрения анализируется реализация Promise.Конечно, функция Promise далека от этого, поэтому в данной статье анализируется принцип реализации распространенных методов Promise. Появление промисов изменило традиционный метод асинхронного программирования, сделав JavaScript более гибким в асинхронном программировании, а код более удобным для сопровождения и читабельным. Поэтому, как перспективный фронтенд, вы должны иметь определенное представление о реализации Promise.