Перевал Node7--harmony_async_await
Parameters начали поддерживать async/await, а async/await известен как рай для асинхронных вызовов, потому что он может писать асинхронные программы в синхронном коде. Однако шаблон обратного вызова Node глубоко укоренился, и эта структурная форма, известная как «ад обратных вызовов», привела к быстрому формированию промисов и ES6. Однако от ада до рая ей ни шагу!
async/await основан на промисе, а не на обратном вызове, поэтому, если вы хотите избавиться от ада обратных вызовов, вы должны сначала изменить реализацию обратного вызова на реализацию промиса — проблема в том, что в Node так много библиотечных функций, а их гораздо больше. сторонние библиотеки Все функции реализованы с помощью обратных вызовов Если вы хотите изменить все на реализацию Promise, проще сказать, чем сделать?
Избавьтесь от ада со сторонними библиотеками
Async
Конечно, должны быть решения, такие какAsyncбиблиотека черезasync.waterfall()
Реализовано "сплющивание" глубинных callback'ов, конечно, это не реализовано с Promise, но в основе лежит его работа по выравниванию, а затем инкапсулируется.Promise
Это уже много лаконичков.
Ниже приведен пример из официальной документации Async.
async.waterfall([
function(callback) {
callback(null, 'one', 'two');
},
function(arg1, arg2, callback) {
// arg1 now equals 'one' and arg2 now equals 'two'
callback(null, 'three');
},
function(arg1, callback) {
// arg1 now equals 'three'
callback(null, 'done');
}
], function (err, result) {
// result now equals 'done'
});
Если вы инкапсулируете его какPromise
Это также легко:
// promiseWaterfall 使用 async.waterfall 处理函数序列
// 并将最终结果封装成 Promise
function promiseWaterfall(series) {
return new Promise((resolve, reject) => {
async.waterfall(series, function(err, result) {
if (err) {
reject(err);
} else {
resolve(result);
}
});
});
}
// 调用示例
promiseWaterfall([
function(callback) {
callback(null, "one", "two");
},
function(arg1, arg2, callback) {
// arg1 now equals 'one' and arg2 now equals 'two'
callback(null, "three");
},
function(arg1, callback) {
// arg1 now equals 'three'
callback(null, "done");
}
]).then(result => {
// result now equals 'done'
});
Q
QЭто также широко используемая библиотека Promise, которая предоставляет ряд служебных функций для обработки обратных вызовов в стиле Node, таких какQ.nfcall(),Q.nfapply(),Q.denodeify()Ждать.
в,Q.denodeify()
, псевдонимQ.nfbind()
, который преобразует функцию обратного вызова Node в функцию типа Promise. Хотя преобразованная функция возвращает не роднойPromise
объект, а одна из внутренних реализаций QPromise
Объект класса, мы можем назвать его похожим на Promise объектом.
Q.denodeify()
Использование очень простое, вы можете напрямую инкапсулировать функцию в стиле Node, и следующий пример также приведен в официальном документе.
var readFile = Q.nfbind(FS.readFile);
readFile("foo.txt", "utf-8").done(function (text) {
// do something with text
});
Здесь следует отметить, что хотяQ.denodeify()
Инкапсулированная функция возвращает объект, похожий на Promise, но автор лично проверил, что его можно использовать для операции ожидания.[注1]
.
[注1]
: ждать вMDNВышеупомянутое описано как «оператор», то есть оператор, поэтому здесь мы говорим «ожидание операции», или мы можем сказать «ожидание выражения».
Bluebird
Для jser,BluebirdНе незнакомый. это проходитPromise.promisify()а такжеPromise.promisifyAll()и т. д. обеспечивают преобразование в функции в стиле Node, такие же, как упомянутые выше.Q.denodeify()
аналогичный. Обратите внимание на упоминание здесьPromise
Это не родной промис, а реализованный bluebird, на который обычно ссылаются со следующим утверждением:
const Promise = require("bluebird").Promise;
Чтобы отличить его от родного промиса, его также можно изменить на
const BbPromise = require("bluebird").Promise;
Promise.promisifyAll()
Он относительно особенный, принимает в качестве параметра объект и обрабатывает все методы этого объекта в стиле промисов. конкретная операция здесь, обратитесь к официальной документации Can.
а такжеQ.denodeify()
Аналогично, через BluebirdPromise.promisify()
илиPromise.promisifyAll()
Обработанная функция возвращает объект, похожий на Promise, и его также можно использовать для операций ожидания.
выбраться из ада самостоятельно
ES6 уже предоставляет нативную реализацию Promise, и кажется бесполезным ссылаться на стороннюю библиотеку только для того, чтобы «выбраться из ада». Если вы можете инкапсулировать стиль обратного вызова в стиль Promise с помощью небольшого количества кода, почему бы не реализовать его самостоятельно?
Давайте проанализируем и напишем самиpromisify()
Что должно быть сделано
[1]>
определениеpromisify()
promisify()
Это функция преобразования, ее параметр представляет собой функцию в стиле обратного вызова, а возвращаемое значение представляет собой функцию в стиле обещания, поэтому независимо от того, является ли это параметром или возвращаемым значением, это функция.
// promisify 的结构
function promisify(func) {
return function() {
// ...
};
}
[2]>
Возвращаемая функция должна возвращатьPromise
объект
теперь, когдаpromisify()
Возвращаемое значение представляет собой функцию в стиле Promise, и ее возвращаемое значение должно бытьPromise
объект, поэтому
function promisify(func) {
return function() {
return new Promise((resolve, reject) => {
// TODO
});
};
}
[3]>
Звонил в обещанииfunc
Излишне говорить, что вышеTODO
Некоторым нужно реализоватьfunc
звоните и звоните соответствующим образом в зависимости от результатаresolve()
а такжеreject()
.
function promisify(func) {
return function() {
return new Promise((resolve, reject) => {
func((err, result) => {
if (err) {
reject(err);
} else {
resolve(result);
}
});
});
};
}
Первым параметром функции обратного вызова стиля обратного вызова Node является объект ошибки, если онnull
значит ошибки нет, значит будет(err, result) => {}
Такое определение обратного вызова.
[4]>
добавить параметры
Приведенный выше вызов не добавил обработку параметров. Для функций обратного вызова Node обычно первые n параметров — это параметры, которые должна использовать внутренняя реализация, а последний параметр — это функция обратного вызова. Простота реализации с использованием вариативного и расширенного синтаксиса данных ES6.
// 最终实现如下
function promisify(func) {
return function(...args) {
return new Promise((resolve, reject) => {
func(...args, (err, result) => {
if (err) {
reject(err);
} else {
resolve(result);
}
});
});
};
}
Пока что полныйpromisify()
Это реализовано.
[5]>
выполнитьpromisifyArray()
promisifyArray()
Используется для пакетной обработки группы функций. Этот параметр представляет собой список функций обратного вызова и возвращает список соответствующих функций в стиле Promise. в реализацииpromisify()
реализуется на основеpromisifyArray()
очень просто.
function promisifyArray(list) {
return list.map(promisify);
}
[6]>
выполнитьpromisifyObject()
promisifyObject()
реализации необходимо учитыватьthis
Проблема указателей относительно сложна, и вышеизложенное нельзя использовать напрямую.promisify()
. НижеpromisifyObject()
Упрощенная реализация , подробности см. в комментариях к коду.
function promisifyObject(obj, suffix = "Promisified") {
// 参照之前的实现,重新实现 promisify。
// 这个函数没用到外层的局部变量,不必实现为局域函数,
// 这里实现为局部函数只是为了组织演示代码
function promisify(func) {
return function(...args) {
return new Promise((resolve, reject) => {
// 注意调用方式的变化
func.call(this, ...args, (err, result) => {
if (err) {
reject(err);
} else {
resolve(result);
}
});
});
};
}
// 先找出所有方法名称,
// 如果需要过滤可以考虑自己加 filter 实现
const keys = [];
for (const key in obj) {
if (typeof obj[key] === "function") {
keys.push(key);
}
}
// 将转换之后的函数仍然附加到原对象上,
// 以确保调用的时候,this 引用正确。
// 为了避免覆盖原函数,加了一个 suffix。
keys.forEach(key => {
obj[`${key}${suffix}`] = promisify(obj[key]);
});
return obj;
}
небо в поле зрения
Из ада, недалеко от рая. мой предыдущий блогПонимание async/await в JavaScriptБыла объяснена связь между async/await и Promise. Выше было использовано много места для реализации преобразования функций в стиле обратного вызова в функции в стиле Promise, поэтому следующее, что нужно сделать, это попрактиковаться в async/await.
Инкапсулируйте функции, связанные с обещанием, в модули
Поскольку он используется в Node, я реализовал его сам.promisify()
,promisifyArray()
а такжеpromisifyObject()
Лучше инкапсулировать его в Node-модуль. Три функции были определены ранее, просто нужно экспортировать
module.exports = {
promisify: promisify,
promisifyArray: promisifyArray,
promisifyObject: promisifyObject
};
// 通过解构对象导入
// const {promisify, promisifyArray, promisifyObject} = require("./promisify");
Поскольку эти три функции независимы, их также можно экспортировать как массивы.
module.exports = [promisify, promisifyArray, promisifyObject];
// 通过解构数组导入
// const [promisify, promisifyArray, promisifyObject] = require("./promisify");
Моделирование сценария приложения
В этом смоделированном сценарии приложения необходимо выполнить операцию, включающую 4 шага (все асинхронные операции).
-
first()
получить идентификатор пользователя -
second()
Получить информацию о пользователе на основе идентификатора пользователя -
third()
Получить оценку пользователя на основе идентификатора пользователя -
last()
Выводить информацию о пользователе и баллы
из которых2
,3
Шаги можно распараллелить.
Структура данных, используемая в этом сценарии, определяется следующим образом.
class User {
constructor(id) {
this._id = id;
this._name = `User_${id}`;
}
get id() {
return this._id;
}
get name() {
return this._name;
}
get score() {
return this._score || 0;
}
set score(score) {
this._score = parseInt(score) || 0;
}
toString() {
return `[#${this._id}] ${this._name}: ${this._score}`;
}
}
Используйте setTimeout для имитации асинхронности
определитьtoAsync()
Для имитации нормальной функции в асинхронную функцию. Я могу написать несколько меньшеsetTimeout()
.
function toAsync(func, ms = 10) {
setTimeout(func, ms);
}
Имитация 4 шагов в стиле обратного вызова
function first(callback) {
toAsync(() => {
// 产生一个 1000-9999 的随机数作为 ID
const id = parseInt(Math.random() * 9000 + 1000);
callback(null, id);
});
}
function second(id, callback) {
toAsync(() => {
// 根据 id 产生一个 User 对象
callback(null, new User(id));
});
}
function third(id, callback) {
toAsync(() => {
// 根据 id 计算一个分值
// 这个分值在 50-100 之间
callback(null, id % 50 + 50);
});
}
function last(user, score, callback) {
toAsync(() => {
// 将分值填入 user 对象
// 输出这个对象的信息
user.score = score;
console.log(user.toString());
if (callback) {
callback(null, user);
}
});
}
Конечно, есть и экспорт
module.exports = [first, second, third, last];
асинхронная/ожидающая практика
const [promisify, promisifyArray, promisifyObject] = require("./promisify");
const [first, second, third, last] = promisifyArray(require("./steps"));
// 使用 async/await 实现
// 用 node 运行的时候需要 --harmoney_async_await 参数
async function main() {
const userId = await first();
// 并行调用要用 Promise.all 将多个并行处理封装成一个 Promise
const [user, score] = await Promise.all([
second(userId),
third(userId)
]);
last(user, score);
}
main();