callback
В JavaScript функции являются первыми гражданами и могут быть переданы в качестве параметров функциям для выполнения. Таким образом, мы можем поместить код, который должен выполняться асинхронно, в функцию обратного вызова, а затем выполнить код в асинхронном обратном вызове.
Example
Определяется следующим образомdelay
функция,callback
Тип параметра — функция, которая будет выполнена через 1 секунду.
function delay(callback) {
console.log('foo');
setTimeout(callback, 1000);
}
delay(console.log.bind(console, 'bar'));
// 控制台立即打印 foo
// 1秒钟后控制台输出 bar
Функция обратного вызова для обработки одной асинхронной операции не кажется проблемой, но представьте себе несколько подобных сценариев.
- Сценарий 1. Предположим, что все задержки A, delayB и delayC являются асинхронными операциями.
callback
Параметры выполняются после завершения операции.Выполненная операция зависит от результата операции delayC, операция delayC зависит от delayB, а delayB зависит от delayA.Как мы это решаем? :
function delayA(callback) {
setTimeout(() => {
callback(1);
}, 1000);
}
function delayB(fn, somthingFromA) {
setTimeout(() => {
callback(somthingFromA + 1);
}, 2000);
}
function delayC(fn, somthingFromB) {
setTimeout(() => {
callback(somthingFromB + 1);
}, 3000);
}
function done(result) {
console.log(result);
}
delayA((feedBackByA) => {
delayB((feedBackByB) => {
delayC((feedBackByC) => {
done(feedBackByC);
}, feedBackByB);
}, feedBackByA);
});
// 6 秒后控制台输出 3
- Сценарий 2: выполняемая операция зависит от результатов операций delayA, delayB и delayC, но между задержкой A, задержкой B и задержкой C нет никакой зависимости. Они определяются следующим образом:
function delayA(callback) {
setTimeout(() => {
callback(1);
}, 1000);
}
function delayB(fn) {
setTimeout(() => {
callback(2);
}, 2000);
}
function delayC(fn) {
setTimeout(() => {
callback(3);
}, 3000);
}
function done(somthingFromA, somthingFromB, somthingFromC) {
console.log(somthingFromA, somthingFromB, somthingFromC);
}
Конечно, мы можем использовать то же решение в сценарии 1, но синхронное выполнение нескольких несвязанных асинхронных операций задерживает время выполнения выполненной операции.
delayA((feedBackByA) => {
delayB((feedBackByB) => {
delayC((feedBackByC) => {
done(feedBackByA, feedBackByB, feedBackByC);
});
});
});
// 6 秒后控制台输出 1, 2, 3
Следовательно, необходимо определить вспомогательную функцию, которая вызывает одновременное выполнение нескольких асинхронных операций и выполняет функцию обратного вызова, когда завершается асинхронная операция с последним обратным вызовом:
/*
* fns: 异步操作函数的数组
* callback: 所有异步操作都完成时的回调
*/
var helper = (fns, callback) => {
// 定义一个 checkList,标识每个异步操作是否完成
const checkList = new Array(fns.length).fill(false);
// 保存每个异步操作返回的参数供 callback 使用
const parameters = new Array(fns.length);
// 判断是否所有的异步操作都已完成
function allCheck() {
return checkList.reduce((prev, val) => {
return prev && val;
}, true);
}
fns.forEach((fn, index) => {
fns((feedBack) => {
parameters[index] = feedBack;
checkList[index] = true;
if (allCheck()) {
callback.apply(null, parameters);
}
});
});
};
helper([delayA, delayB, delayC], done);
// 3 秒后控制台输出 1, 2, 3
Представьте, что требования сценария 1 и сценария 2 объединены, и код станет слишком глубоко вложенным и трудным для чтения. Подводя итог, можно сказать, что чрезмерное использование функций обратного вызова может привести к легендарному аду обратных вызовов, поскольку зависимости асинхронных операций усложняются, а код становится уродливым и неуправляемым.
Promise
Промисы были введены в ES6 и часто используются для борьбы с асинхронностью Давайте посмотрим, как это упрощает наш процесс работы с асинхронностью.
Студенты, которые не знают Promise, могут сначала посмотретьdeveloper.Mozilla.org/en-US/docs/…
Во-первых, давайте посмотрим на преобразование предыдущей асинхронной функции в промис:
funcion promisify(fn) {
return (args) =>
return new Promise((resolve, reject) => {
// fn 是一个异步操作,第一参数是回调函数,第二个参数是执行的参数
fn(resolve, args);
});
};
}
Таким образом, исходная асинхронная операция с использованием обратного вызова в качестве параметра выглядит следующим образом:
function delay(callback, content) {
setTimeout(() => callback(content), 1000);
}
может быть обещано:
const delayPromise = promisify(delay);
delayPromise('foo').then((content) => {
console.log(content);
});
// 1 秒后控制台输出 foo
Кажется, хотя она ничем не отличается от функции обратного вызова, давайте взглянем на приведенный выше сценарий 1 и на то, что произойдет с нашим решением с использованием Promise.
promisify(delayA)()
.then((feedbackFromA) => {
return promisify(delayB)(feedbackFromA);
})
.then((feedbackFromB) => {
return promisify(delayC)(feedbackFromB);
})
.then((feedbackFromC) => {
done(feedbackFromC);
});
// 6 秒后控制台输出 3
Посмотрите на вторую сцену:
Promise.all([
promisify(delayA)(),
promisify(delayB)(),
promisify(delayC)()
]).then(([feedbackFromA, feedbackFromB, feedbackFromC]) => {
done(feedbackFromA, feedbackFromB, feedbackFromC);
});
// 3 秒后控制台打印 1,2,3
Как только все асинхронные операции будут инкапсулированы как объекты Promise, очевидно, что больше не будет проблем с адскими обратными вызовами.
Generator
Generator
Также концепция, представленная в ES6. дляGenerator
Для студентов, которые не знают, сначала проверьте документdeveloper.Mozilla.org/en-US/docs/…
Сначала давайте посмотрим наGenerator
Как это работает:
function* main() {
const a = yield 1; // a 为第二次调用 next 时传入的参数
const b = yield 2 + a; // b 为第三次调用 next 时传入的参数
yield 3 + b;
return 4;
}
const gen = main();
gen.next(); // {value: 1, done: false}
gen.next(1); // {value: 3, done: false}
gen.next(5); // {value: 8, done: false}
gen.next(); // {value: 4, done: true}
Generator
Как решить асинхронность? Представьте, что у нас есть вспомогательная функцияhelper
функция, позволяющая выполнять следующиеgenerator
После завершения автоматического выполнения писать асинхронный код в будущем может быть так же удобно, как и писать синхронный код.
helper(function* main(args) {
const feedBackFromA = yield promisify(delayA)(args);
const feedBackFromB = yield promisify(delayB)(feedBackFromA);
const feedBackFromC = yield promisify(delayC)(feedBackFromB);
done(feedBackFromC);
})(realArgs);
этоhelper
Функция должна удовлетворять следующим условиям:
- автоматическое выполнение
generator
доgenerator
статус завершен -
yield
после ключевого словаPromise
Значение разрешения возвращается как параметр для следующего вызова next. - Обещание возврата, используйте
generator
Возвращаемое значение экземпляра используется как значение разрешения
Поэтому определите помощника следующим образом:
function helper(genFn) {
return (...args) => new Promise(resolve, reject) => {
let gen = genFn(args);
function next(prev) => {
const {
value,
done
} = gen.next(prev);
if(done) {
return resolve(next.value);
} else {
return value.then(next);
}
}
next();
});
}
Так
helper(main)();
// 6 秒后控制台打印 3
Тогда проблема сценария 2 может быть решена с помощью этого помощника:
helper(function* main() {
const [feedBackFromA, feedBackFromB, feedBackFromC] = yield Promise.all([
promisify(delayA)();
promisify(delayB)();
promisify(delayC)();
]);
d(feedBackFromA, feedBackFromB, feedBackFromC);
})();
// 3 秒后控制台打印 1,2,3
Желающие могут обработать это самостоятельноhelper
, чтобы он поддерживал следующий синтаксис:
helper(function* main() {
const [feedBackFromA, feedBackFromB, feedBackFromC] = yield [
promisify(delayA)();
promisify(delayB)();
promisify(delayC)();
];
d(feedBackFromA, feedBackFromB, feedBackFromC);
})();
Вот хорошая сторонняя библиотека, рекомендуемаяco
, что дает очень мощныйhelper
реализовать егоyield
Позже он может принимать Array, Promise, Function и многие другие типы.
Async Function
Нативная поддержка Async-функций была добавлена в ES7.
Таким образом, метод написания очень похож на решение с генератором выше, но только объекты Promise принимаются после ключевого слова await.
Решение для асинхронной функции:
сцена первая:
async function main() {
const feedBackFromA = await promisify(delayA)();
const feedBackFromB = await promisify(delayB)(feedBackFromA);
const feedBackFromC = await promisify(delayC)(feedBackFromB);
d(feedBackFromC);
}
Сценарий второй:
async function main() {
const feedBacks = await Promise.all([
promisify(delayA)(/* a的参数 */);
promisify(delayB)(/* b的参数 */);
promisify(delayC)(/* c的参数 */);
]);
d(feedBacks);
}
обработка ошибок
callback
Обрабатывать ошибки в соответствующих обратных вызовах.
a(() => {
if (err) {
// handle the error of a
}
b(() => {
if (err) {
// handle the error of b
}
...
});
});
Promise
Используйте метод catch, чтобы получить унифицированную обработку ошибок во всем процессе Promise, или обрабатывайте их по отдельности во втором параметре метода then.
promisify(delayA)()
.then(promisify(delayB), (err) => {
// handle the error of a
})
.catch((err) => {
// handle all errors
});
Генераторы и асинхронные функции
Метод записи близок к синхронизации, и вы можете напрямую использовать try catch для обработки ошибок.
async function main() {
try {
const feedBackFromA = await promisify(delayA)(/* a的参数 */);
const feedBackFromB = await promisify(delayB)(aRes);
const feedBackFromC = await promisify(delayC)(feedBackFromB);
d(feedBackFromC);
} catch (err) {
// handle all errors
}
}
В этой работе используетсяCreative Commons Attribution-NonCommercial-ShareAlike 4.0 Международная лицензияЛицензия.