Как преобразовать существующий API с обратными вызовами в промисы?
Я хочу работать с промисами, но у меня есть API, который использует колбэки в следующих форматах:
1. Загрузка DOM или другое одноразовое событие:
window.onload; // установить колбэк
...
window.onload = function() {
};
2. Простой колбэк:
function request(onChangeHandler) {
...
}
request(function() {
// произошло изменение
...
});
3. Колбэк в стиле Node ("nodeback"):
function getStuff(dat, callback) {
...
}
getStuff("dataParam", function(err, data) {
...
});
4. Целая библиотека с колбэками в стиле Node:
API;
API.one(function(err, data) {
API.two(function(err, data2) {
API.three(function(err, data3) {
...
});
});
});
Как я могу работать с этими API, используя промисы? Как мне "промисифицировать" их?
5 ответ(ов)
Чтобы преобразовать функцию, использующую обратные вызовы, в промис в Node.js, можно следовать приведенному примеру. Ниже показан процесс преобразования:
Перед преобразованием в промис:
var request = require('request'); // Модуль для работы с HTTP
function requestWrapper(url, callback) {
request.get(url, function (err, response) {
if (err) {
callback(err);
} else {
callback(null, response);
}
})
}
requestWrapper(url, function (err, response) {
console.log(err, response)
})
В этом коде используется функция requestWrapper
, которая принимает URL и функцию обратного вызова. При получении ответа функция вызывает обратный вызов, передавая ошибку или ответ.
После преобразования в промис:
var request = require('request');
function requestWrapper(url) {
return new Promise(function (resolve, reject) { // Возвращаем промис
request.get(url, function (err, response) {
if (err) {
reject(err); // Отклоняем промис
} else {
resolve(response); // Исполняем промис
}
})
})
}
requestWrapper('http://localhost:8080/promise_request/1').then(function(response){
console.log(response); // Обработка успешного ответа
}).catch(function(error){
console.log(error); // Обработка ошибки
})
В этом примере requestWrapper
теперь возвращает промис. При получении ошибки промис отклоняется, а в случае успешного ответа — исполняется.
Если вам нужно обработать несколько запросов:
var allRequests = [];
allRequests.push(requestWrapper('http://localhost:8080/promise_request/1'))
allRequests.push(requestWrapper('http://localhost:8080/promise_request/2'))
allRequests.push(requestWrapper('http://localhost:8080/promise_request/5'))
Promise.all(allRequests).then(function (results) {
console.log(results); // Результат будет массивом, содержащим ответы каждого промиса
}).catch(function (err) {
console.log(err);
});
В этом примере мы создаем массив allRequests
, в который добавляем несколько запросов, и затем используем Promise.all
, чтобы обработать их параллельно. Если все запросы выполнены успешно, мы получаем массив результатов; в случае ошибки — обработка ошибки через catch
.
Таким образом, использование промисов позволяет более удобно управлять асинхронной логикой в Node.js.
Я не думаю, что предложение использовать window.onload
от @Benjamin будет работать всегда, так как оно не проверяет, было ли вызвано событие после полной загрузки. У меня были с этим проблемы много раз. Вот версия, которая всегда должна работать:
function promiseDOMready() {
return new Promise(function(resolve) {
if (document.readyState === "complete") return resolve();
document.addEventListener("DOMContentLoaded", resolve);
});
}
promiseDOMready().then(initOnLoad);
Этот код создает промис, который разрешается, когда DOM полностью загружен. Если состояние документа уже "complete", промис сразу же разрешается. В противном случае добавляется обработчик события DOMContentLoaded
, который разрешит промис, когда событие произойдет. Таким образом, вы всегда получите гарантированное срабатывание обработчика, независимо от времени вызова.
Простая обобщённая функция, которую я обычно использую
const promisify = (fn, ...args) => {
return new Promise((resolve, reject) => {
fn(...args, (err, data) => {
if (err) {
return reject(err);
}
resolve(data);
});
});
};
Как её использовать
- Функция
promisify
принимает функцию с обратным вызовом:
const cb = (result) => `Результат: ${result}`;
const sum = (a, b, cb) => {
const result = a + b;
cb(result); // передача аргументов в функцию обратного вызова
}
// Использование утилиты
const promise = promisify(sum, 3, 1, cb);
promise.then(x => console.log(x)); // 4
Вы, возможно, не ищете этот ответ, но он поможет понять внутреннее устройство доступных утилит.
Вы можете использовать нативные промисы JavaScript с Node.js.
Вот пример кода, который демонстрирует, как можно использовать промисы в вашем приложении на Node.js с Express:
/**
* Created by dixit-lab on 20/6/16.
*/
var express = require('express');
var request = require('request'); // Упрощенный HTTP-клиент.
var app = express();
function promisify(url) {
return new Promise(function (resolve, reject) {
request.get(url, function (error, response, body) {
if (!error && response.statusCode == 200) {
resolve(body);
}
else {
reject(error);
}
});
});
}
// Получить все альбомы пользователя, который опубликовал пост с ID 100
app.get('/listAlbums', function (req, res) {
// Получаем пост с ID 100
promisify('http://jsonplaceholder.typicode.com/posts/100').then(function (result) {
var obj = JSON.parse(result);
return promisify('http://jsonplaceholder.typicode.com/users/' + obj.userId + '/albums');
})
.catch(function (e) {
console.log(e);
res.status(500).send('Ошибка при получении данных.');
})
.then(function (result) {
res.end(result);
});
});
var server = app.listen(8081, function () {
var host = server.address().address;
var port = server.address().port;
console.log("Пример приложения запущен по адресу http://%s:%s", host, port);
});
// Запустите веб-сервис в браузере: http://localhost:8081/listAlbums
Обратите внимание на функцию promisify
, которая возвращает промис, использующий библиотеку request
для выполнения HTTP-запросов. В эндпоинте /listAlbums
мы сначала получаем пост с ID 100, затем, используя userId
из этого поста, запрашиваем все альбомы пользователя. Ошибки обрабатываются с помощью .catch
, и в случае их возникновения клиент получает сообщение об ошибке с статусом 500.
Чтобы преобразовать колбэк API в промисы с помощью простого JavaScript, можно использовать следующий подход. Рассмотрим функцию get
, которая выполняет запрос к указанному URL и использует колбэк для обработки результата.
function get(url, callback) {
var xhr = new XMLHttpRequest();
xhr.open('get', url);
xhr.addEventListener('readystatechange', function () {
if (xhr.readyState === 4) {
if (xhr.status === 200) {
console.log('успех ... вызываем колбэк ...');
callback(null, JSON.parse(xhr.responseText));
} else {
console.log('ошибка ... вызываем колбэк с данными об ошибке ...');
callback(xhr, null);
}
}
});
xhr.send();
}
/**
* @function promisify: преобразует функции с колбэками в промисы
* @description принимает функцию и преобразует её в промис
* @params {function} fn функция для преобразования
* @params {array} args массив аргументов для функции
* @return {function} преобразованная в промис функция
*/
function promisify(fn) {
return function () {
var args = Array.prototype.slice.call(arguments);
return new Promise(function(resolve, reject) {
fn.apply(null, args.concat(function (err, result) {
if (err) reject(err);
else resolve(result);
}));
});
}
}
var get_promisified = promisify(get);
var promise = get_promisified('some_url');
promise.then(function (data) {
// соответствует функции resolve
console.log('успешная операция: ', data);
}, function (error) {
console.log(error);
});
В этом коде мы создали функцию get
, которая использует XMLHttpRequest для обработки HTTP-запросов. Затем мы создали функцию promisify
, которая принимает эту функцию и возвращает новую функцию, которая возвращает промис. При вызове get_promisified
возвращается промис, позволяя использовать then
для обработки успешного или ошибочного результата.
Использование async/await с циклом forEach
Как получить правильный `this` внутри колбэка?
Функция map для объектов (вместо массивов)
Как получить полный объект в console.log() Node.js, а не '[Object]'?
Как прочитать JSON-файл в память сервера?