Как преобразовать существующий API обратного вызова в обещания?
Я хочу работать с обещаниями, но у меня есть API обратного вызова в таком формате:
1. Загрузка DOM или другое одноразовое событие:
window.onload; // set to callback
...
window.onload = function() {
};
2. Простой обратный вызов:
function request(onChangeHandler) {
...
}
request(function() {
// change happened
...
});
3. Обратный вызов стиля узла ("nodeback"):
function getStuff(dat, callback) {
...
}
getStuff("dataParam", function(err, data) {
...
})
4. Целая библиотека с обратными вызовами в стиле узла:
API;
API.one(function(err, data) {
API.two(function(err, data2) {
API.three(function(err, data3) {
...
});
});
});
Как мне работать с API в обещаниях, как его "обещать"?
26 ответов
Обещания имеют статус, они начинаются как ожидающие и могут рассчитывать на:
- означает, что вычисление завершено успешно.
- отклонено означает, что вычисление не удалось.
Функции возврата обещаний никогда не должны выдаваться, вместо этого они должны возвращать отклонения. Функция возврата из обещания заставит вас использовать как } catch {
и .catch
, Люди, использующие обещанные API-интерфейсы, не ожидают обещаний. Если вы не уверены, как работают асинхронные API в JS - сначала посмотрите этот ответ.
1. Загрузка DOM или другое одноразовое событие:
Таким образом, создание обещаний, как правило, означает указание, когда они рассчитываются, то есть когда они переходят к выполненной или отклоненной фазе, чтобы указать, что данные доступны (и могут быть доступны с помощью .then
).
С современными реализациями обещаний, которые поддерживают Promise
Конструктор как родной ES6 обещает:
function load() {
return new Promise(function(resolve, reject) {
window.onload = resolve;
});
}
Затем вы будете использовать полученное обещание так:
load().then(function() {
// Do things after onload
});
С библиотеками, которые поддерживают отложенный (давайте используем здесь $q для этого примера, но позже мы также будем использовать jQuery):
function load() {
var d = $q.defer();
window.onload = function() { d.resolve(); };
return d.promise;
}
Или с помощью jQuery-подобного API, перехватывая событие, происходящее один раз:
function done() {
var d = $.Deferred();
$("#myObject").once("click",function() {
d.resolve();
});
return d.promise();
}
2. Простой обратный вызов:
Эти API довольно распространены, так как... обратные вызовы распространены в JS. Давайте посмотрим на общий случай onSuccess
а также onFail
:
function getUserData(userId, onLoad, onFail) { …
С современными реализациями обещаний, которые поддерживают Promise
Конструктор как родной ES6 обещает:
function getUserDataAsync(userId) {
return new Promise(function(resolve, reject) {
getUserData(userId, resolve, reject);
});
}
С библиотеками, которые поддерживают отложенный (давайте используем здесь jQuery, но мы также использовали $q выше):
function getUserDataAsync(userId) {
var d = $.Deferred();
getUserData(userId, function(res){ d.resolve(res); }, function(err){ d.reject(err); });
return d.promise();
}
JQuery также предлагает $.Deferred(fn)
преимущество в том, что мы можем написать выражение, которое очень близко имитирует new Promise(fn)
форма, следующим образом:
function getUserDataAsync(userId) {
return $.Deferred(function(dfrd) {
getUserData(userId, dfrd.resolve, dfrd.reject);
}).promise();
}
Примечание: здесь мы используем тот факт, что JQuery отсроченный resolve
а также reject
методы "отрывные"; то есть. они привязаны к экземпляру jQuery.Deferred(). Не все библиотеки предлагают эту функцию.
3. Обратный вызов стиля узла ("nodeback"):
Обратные вызовы в стиле узла (обратные вызовы узлов) имеют определенный формат, где обратные вызовы всегда являются последним аргументом, а его первый параметр является ошибкой. Давайте сначала пообещаем одно вручную:
getStuff("dataParam", function(err, data) { …
Для того, чтобы:
function getStuffAsync(param) {
return new Promise(function(resolve, reject) {
getStuff(param, function(err, data) {
if (err !== null) reject(err);
else resolve(data);
});
});
}
С deferreds вы можете сделать следующее (давайте используем Q для этого примера, хотя Q теперь поддерживает новый синтаксис, который вы должны предпочесть):
function getStuffAsync(param) {
var d = Q.defer();
getStuff(param, function(err, data) {
if (err !== null) d.reject(err);
else d.resolve(data);
});
return d.promise;
}
В общем, вам не следует слишком много обещать вручную, в большинстве библиотек обещаний, которые были разработаны с учетом Node, а также в собственных обещаниях в Node 8+ есть встроенный метод для обещания обратных узлов. Например
var getStuffAsync = Promise.promisify(getStuff); // Bluebird
var getStuffAsync = Q.denodeify(getStuff); // Q
var getStuffAsync = util.promisify(getStuff); // Native promises, node only
4. Целая библиотека с обратными вызовами в стиле узла:
Здесь нет золотого правила, вы обещаете их одно за другим. Тем не менее, некоторые реализации обещаний позволяют вам делать это массово, например, в Bluebird, преобразование API backback в API обещаний так же просто, как:
Promise.promisifyAll(API);
Или с нативными обещаниями в Node:
const { promisify } = require('util');
const promiseAPI = Object.entries(API).map(v => ({key, fn: promisify(v)}))
.reduce((o, p) => Object.assign(o, {[p.key]: p.fn}), {});
Заметки:
- Конечно, когда вы находитесь в
.then
Хендлер, вам не нужно обещать вещи. Возвращая обещание от.then
Обработчик разрешит или отклонит значение этого обещания. Бросать из.then
Обработчик также является хорошей практикой и будет отклонять обещание - это знаменитое обещание безопасности. - В актуальном
onload
случай, вы должны использоватьaddEventListener
скорее, чемonX
,
Сегодня я могу использовать Promise
в Node.js
как простой метод Javascript.
Простой и основной пример Promise
(с поцелуем):
Простой Javascript Async API-код:
function divisionAPI (number, divider, successCallback, errorCallback) {
if (divider == 0) {
return errorCallback( new Error("Division by zero") )
}
successCallback( number / divider )
}
Promise
Код Javascript Async API:
function divisionAPI (number, divider) {
return new Promise(function (fulfilled, rejected) {
if (divider == 0) {
return rejected( new Error("Division by zero") )
}
fulfilled( number / divider )
})
}
(Я рекомендую посетить этот прекрасный источник)
Также Promise
можно использовать вместе async\await
в ES7
заставить поток программы ждать fullfiled
результат как следующий:
function getName () {
return new Promise(function (fulfilled, rejected) {
var name = "John Doe";
// wait 3000 milliseconds before calling fulfilled() method
setTimeout (
function() {
fulfilled( name )
},
3000
)
})
}
async function foo () {
var name = await getName(); // awaits for a fulfilled result!
console.log(name); // the console writes "John Doe" after 3000 milliseconds
}
foo() // calling the foo() method to run the code
Другое использование с тем же кодом с помощью .then()
метод
function getName () {
return new Promise(function (fulfilled, rejected) {
var name = "John Doe";
// wait 3000 milliseconds before calling fulfilled() method
setTimeout (
function() {
fulfilled( name )
},
3000
)
})
}
// the console writes "John Doe" after 3000 milliseconds
getName().then(function(name){ console.log(name) })
Promise
также может использоваться на любой платформе, основанной на Node.js, например react-native
,
Надеюсь это поможет.
Перед преобразованием функции в качестве обещания в Node.JS
var request = require('request'); //http wrapped module
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)
})
После преобразования
var request = require('request');
var Promise = require('bluebird');
function requestWrapper(url) {
return new Promise(function (resolve, reject) { //returning promise
request.get(url, function (err, response) {
if (err) {
reject(err); //promise reject
}else{
resolve(response); //promise resolve
}
})
})
}
requestWrapper('http://localhost:8080/promise_request/1').then(function(response){
console.log(response) //resolve callback(success)
}).catch(function(error){
console.log(error) //reject callback(failure)
})
Incase вам нужно обработать несколько запросов
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);//result will be array which contains each promise response
}).catch(function (err) {
console.log(err)
});
Node.js 8.0.0 включает в себя новый util.promisify()
API, который позволяет обернуть стандартные API стиля обратного вызова Node.js в функцию, которая возвращает Promise. Пример использования util.promisify()
показано ниже.
const fs = require('fs');
const util = require('util');
const readfile = util.promisify(fs.readFile);
readfile('/some/file')
.then((data) => { /** ... **/ })
.catch((err) => { /** ... **/ });
Из будущего
Простая универсальная функция, которую я обычно использую.
const promisify = (fn, ...args) => {
return new Promise((resolve, reject) => {
fn(...args, (err, data) => {
if (err) {
return reject(err);
}
resolve(data);
});
});
};
Как это использовать
promisify(fn, arg1, arg2)
Вы, вероятно, не ждете этого ответа, но это поможет понять внутреннюю работу доступных утилит.
Я не думаю, что window.onload
Предложение @Benjamin будет работать постоянно, так как оно не определяет, вызывается ли оно после загрузки. Я был укушен этим много раз. Вот версия, которая всегда должна работать:
function promiseDOMready() {
return new Promise(function(resolve) {
if (document.readyState === "complete") return resolve();
document.addEventListener("DOMContentLoaded", resolve);
});
}
promiseDOMready().then(initOnLoad);
В выпуске кандидата на Node.js 8.0.0 есть новая утилита, util.promisify
(Я написал об util.promisify), который заключает в себе способность обещать любую функцию.
Он не сильно отличается от подходов, предложенных в других ответах, но имеет то преимущество, что является основным методом и не требует дополнительных зависимостей.
const fs = require('fs');
const util = require('util');
const readFile = util.promisify(fs.readFile);
Тогда у вас есть readFile
метод, который возвращает нативный Promise
,
readFile('./notes.txt')
.then(txt => console.log(txt))
.catch(...);
С простым старым ванильным javaScript вот решение для обещания обратного вызова API.
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('successful ... should call callback ... ');
callback(null, JSON.parse(xhr.responseText));
} else {
console.log('error ... callback with error data ... ');
callback(xhr, null);
}
}
});
xhr.send();
}
/**
* @function promisify: convert api based callbacks to promises
* @description takes in a factory function and promisifies it
* @params {function} input function to promisify
* @params {array} an array of inputs to the function to be promisified
* @return {function} promisified 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) {
// corresponds to the resolve function
console.log('successful operation: ', data);
}, function (error) {
console.log(error);
});
Вы можете использовать собственные обещания JavaScript с Node JS.
Ссылка на код моего облака 9: https://ide.c9.io/adx2803/native-promises-in-node
/**
* Created by dixit-lab on 20/6/16.
*/
var express = require('express');
var request = require('request'); //Simplified HTTP request client.
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);
}
})
});
}
//get all the albums of a user who have posted post 100
app.get('/listAlbums', function (req, res) {
//get the post with post 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);
})
.then(function (result) {
res.end(result);
})
})
var server = app.listen(8081, function () {
var host = server.address().address
var port = server.address().port
console.log("Example app listening at http://%s:%s", host, port)
})
//run webservice on browser : http://localhost:8081/listAlbums
Библиотека Q от kriskowal включает функции обратного вызова для обещания. Такой метод:
obj.prototype.dosomething(params, cb) {
...blah blah...
cb(error, results);
}
можно конвертировать с помощью Q.ninvoke
Q.ninvoke(obj,"dosomething",params).
then(function(results) {
});
Под узлом v7.6+, который имеет встроенные обещания и асинхронность:
// promisify.js
let promisify = fn => (...args) =>
new Promise((resolve, reject) =>
fn(...args, (err, result) => {
if (err) return reject(err);
return resolve(result);
})
);
module.exports = promisify;
Как пользоваться:
let readdir = require('fs').readdir;
let promisify = require('./promisify');
let readdirP = promisify(readdir);
async function myAsyncFn(path) {
let entries = await readdirP(path);
return entries;
}
Если у вас есть несколько функций, которые принимают обратный вызов, и вы хотите, чтобы они вместо этого возвращали обещание, вы можете использовать эту функцию для выполнения преобразования.
function callbackToPromise(func){
return function(){
// change this to use what ever promise lib you are using
// In this case i'm using angular $q that I exposed on a util module
var defered = util.$q.defer();
var cb = (val) => {
defered.resolve(val);
}
var args = Array.prototype.slice.call(arguments);
args.push(cb);
func.apply(this, args);
return defered.promise;
}
}
В Node.js 8 вы можете обещать методы объекта на лету, используя этот модуль npm:
https://www.npmjs.com/package/doasync
Он использует util.promisify и Proxies, чтобы ваши объекты оставались неизменными. Мемоизация также выполняется с использованием WeakMaps). Вот некоторые примеры:
С объектами:
const fs = require('fs');
const doAsync = require('doasync');
doAsync(fs).readFile('package.json', 'utf8')
.then(result => {
console.dir(JSON.parse(result), {colors: true});
});
С функциями:
doAsync(request)('http://www.google.com')
.then(({body}) => {
console.log(body);
// ...
});
Вы даже можете использовать родной call
а также apply
связать некоторый контекст:
doAsync(myFunc).apply(context, params)
.then(result => { /*...*/ });
Вы можете сделать что-то вроде этого
// @flow
const toPromise = (f: (any) => void) => {
return new Promise<any>((resolve, reject) => {
try {
f((result) => {
resolve(result)
})
} catch (e) {
reject(e)
}
})
}
export default toPromise
Тогда используйте это
async loadData() {
const friends = await toPromise(FriendsManager.loadFriends)
console.log(friends)
}
Функция стиля обратного вызова всегда такая (почти все функции в node.js - это стиль):
//fs.readdir(path[, options], callback)
fs.readdir('mypath',(err,files)=>console.log(files))
Этот стиль имеет ту же особенность:
функция обратного вызова передается последним аргументом.
функция обратного вызова всегда принимает объект ошибки в качестве первого аргумента.
Таким образом, вы можете написать функцию для преобразования функции с этим стилем следующим образом:
const R =require('ramda')
/**
* A convenient function for handle error in callback function.
* Accept two function res(resolve) and rej(reject) ,
* return a wrap function that accept a list arguments,
* the first argument as error, if error is null,
* the res function will call,else the rej function.
* @param {function} res the function which will call when no error throw
* @param {function} rej the function which will call when error occur
* @return {function} return a function that accept a list arguments,
* the first argument as error, if error is null, the res function
* will call,else the rej function
**/
const checkErr = (res, rej) => (err, ...data) => R.ifElse(
R.propEq('err', null),
R.compose(
res,
R.prop('data')
),
R.compose(
rej,
R.prop('err')
)
)({err, data})
/**
* wrap the callback style function to Promise style function,
* the callback style function must restrict by convention:
* 1. the function must put the callback function where the last of arguments,
* such as (arg1,arg2,arg3,arg...,callback)
* 2. the callback function must call as callback(err,arg1,arg2,arg...)
* @param {function} fun the callback style function to transform
* @return {function} return the new function that will return a Promise,
* while the origin function throw a error, the Promise will be Promise.reject(error),
* while the origin function work fine, the Promise will be Promise.resolve(args: array),
* the args is which callback function accept
* */
const toPromise = (fun) => (...args) => new Promise(
(res, rej) => R.apply(
fun,
R.append(
checkErr(res, rej),
args
)
)
)
Для краткости в приведенном выше примере используется ramda.js. Ramda.js - отличная библиотека для функционального программирования. В приведенном выше коде мы использовали его применить (как JavaScript function.prototype.apply
) и добавить (как JavaScript function.prototype.push
). Итак, теперь мы можем преобразовать функцию стиля обратного вызова в функцию обещания стиля:
const {readdir} = require('fs')
const readdirP = toPromise(readdir)
readdir(Path)
.then(
(files) => console.log(files),
(err) => console.log(err)
)
Функции toPromise и checkErr принадлежат библиотеке berserk, это функциональная вилка библиотеки программирования от ramda.js (созданная мной).
Надеюсь, что этот ответ полезен для вас.
Вы можете использовать встроенный Promise в ES6, например, для работы с setTimeout:
enqueue(data) {
const queue = this;
// returns the Promise
return new Promise(function (resolve, reject) {
setTimeout(()=> {
queue.source.push(data);
resolve(queue); //call native resolve when finish
}
, 10); // resolve() will be called in 10 ms
});
}
В этом примере Обещание не имеет причин проваливаться, поэтому reject()
никогда не называется.
es6-promisify
преобразует основанные на обратном вызове функции в основанные на Promise функции.
const promisify = require('es6-promisify');
const promisedFn = promisify(callbackedFn, args);
Ниже приведена реализация того, как функция (API обратного вызова) может быть преобразована в обещание.
function promisify(functionToExec) {
return function() {
var array = Object.values(arguments);
return new Promise((resolve, reject) => {
array.push(resolve)
try {
functionToExec.apply(null, array);
} catch (error) {
reject(error)
}
})
}
}
// USE SCENARIO
function apiFunction (path, callback) { // Not a promise
// Logic
}
var promisedFunction = promisify(apiFunction);
promisedFunction('path').then(()=>{
// Receive the result here (callback)
})
// Or use it with await like this
let result = await promisedFunction('path');
Моя перспективная версия callback
функция является P
функция:
var P = function() {
var self = this;
var method = arguments[0];
var params = Array.prototype.slice.call(arguments, 1);
return new Promise((resolve, reject) => {
if (method && typeof(method) == 'function') {
params.push(function(err, state) {
if (!err) return resolve(state)
else return reject(err);
});
method.apply(self, params);
} else return reject(new Error('not a function'));
});
}
var callback = function(par, callback) {
var rnd = Math.floor(Math.random() * 2) + 1;
return rnd > 1 ? callback(null, par) : callback(new Error("trap"));
}
callback("callback", (err, state) => err ? console.error(err) : console.log(state))
callback("callback", (err, state) => err ? console.error(err) : console.log(state))
callback("callback", (err, state) => err ? console.error(err) : console.log(state))
callback("callback", (err, state) => err ? console.error(err) : console.log(state))
P(callback, "promise").then(v => console.log(v)).catch(e => console.error(e))
P(callback, "promise").then(v => console.log(v)).catch(e => console.error(e))
P(callback, "promise").then(v => console.log(v)).catch(e => console.error(e))
P(callback, "promise").then(v => console.log(v)).catch(e => console.error(e))
P
Функция требует, чтобы подпись обратного вызова была callback(error,result)
,
Обещания всегда имеют
resolve
и
reject
. Когда вы пишете асинхронную оболочку, просто вызовите разрешение, и все готово.
Вы можете написать функцию-оболочку практически для любой функции, принимающей обратный вызов, например:
const myAsyncWrapper = (...params) =>
new Promise((resolve, reject) =>
someFunctionWithCallback(...params, (error, response) =>
error ? reject(error) : resolve(response)
)
);
Вы можете пойти дальше, чтобы написать функцию преобразования обратных вызовов в промисы:
const promisify =
(functionWithCallback) =>
(...params) =>
new Promise((resolve, reject) =>
functionWithCallback(...params, (error, response) =>
error ? reject(error) : resolve(response)
)
);
Эта концепция функций-оболочек особенно полезна при использовании старых библиотек или пакетов SDK. Например, рассмотрим SDK JavaScript API Graph Facebook, который использует аналогичную структуру обратного вызова для выполнения запросов API.
FB.api(apiURL, options, function (request) {
if (request.error || !request) return;
// handle request
});
В современных приложениях гораздо полезнее использовать API на основе промисов. Если вы используете функцию только один или два раза, может быть лучше обещать ответ индивидуально:
// in an async function
const response = await new Promise((resolve, reject) =>
FB.api(apiURL, (res) => (res?.error ? reject(res?.error) : resolve(res)))
);
Если вы часто используете эту функцию, вы можете использовать ту же концепцию оболочки, чтобы написать такую функцию:
const apiWrapper = (...params) =>
new Promise((resolve, reject) =>
FB.api(...params, (res) => (res?.error ? reject(res?.error) : resolve(res)))
);
Хотя промисификаторы иногда хороши, они не будут работать в таких конкретных случаях, как этот. В такие времена поищите на Github современную оболочку или напишите свою собственную, подобную этой.
Немного некромантии, немного может пригодиться эта ссылка ....
TL; DR; посмотрите пример фрагмента в конце этого ответа
функции записи / преобразования, которые можно назвать ожидающими
а
cb(error,result)
или же
new Promise (...)
формат
-
promiseToCB
преобразует и экспортирует существующую функцию, которая ранее была закодирована для возврата обещания -
cbToPromise
преобразует и экспортирует существующую функцию, которая ранее была закодирована для вызова последнего аргумента с помощью (error,result)- если обернутая функция предоставляет более 1 результата, результатом будет массив результатов
- например
cb(undefined,path,stat)
--->resolve([path,stat])
/cb(undefined,[path,stat])
-
asPromise
позволяет вам закодировать новую функцию для возврата обещания, но ее можно вызвать в любом случае -
asCallback
позволяет вам закодировать новую функцию для вызоваcb(err,result)
, но его можно вызвать в любом случае
примеры функций
каждый образец принимает 2 аргумента и разрешает / отклоняет / ошибки на основе случайного числа.
arg2 также может использоваться для принудительного прохождения или отказа. (ищет "-pass" или "-fail").
обернуть существующие функции
- экспортирует функцию в текущее "это" (или используйте
promiseToCB(function myFunc(){},newThis);
)
promiseToCB(function sampleFunc1(arg1,arg2) {
console.log("deciding:",arg1,arg2);
return new Promise(function(resolve,reject){
const timer = setTimeout(function(){reject([arg1,arg2,"ouch"].join("-"));},5000);
setTimeout(function(){
if (arg2.endsWith("-pass") || (!arg2.endsWith("-fail") && Math.random()<0.5)) {
console.log("complete:",arg1,arg2);
clearTimeout(timer);
resolve([arg1,arg2,"all good"].join("-"));
}
},2000);
});
});
cbToPromise('sampleFunc2',function someOtherName(arg1,arg2,cb) {
console.log("deciding:",arg1,arg2);
const timer = setTimeout(function(){cb([arg1,arg2,"ouch"].join("-"));},5000);
setTimeout(function(){
if (arg2.endsWith("-pass") || (!arg2.endsWith("-fail") && Math.random()<0.5)) {
console.log("complete:",arg1,arg2);
clearTimeout(timer);
cb(undefined,[arg1,arg2,"all good"].join("-"));
}
},2000);
},local);
или кодируйте новые функции, в которые встроена оболочка.
function sampleFunc3(arg1,arg2) {return asPromise(arguments,function(resolve,reject){
console.log("deciding:",arg1,arg2);
const timer = setTimeout(function(){reject([arg1,arg2,"ouch"].join("-"));},5000);
setTimeout(function(){
if (arg2.endsWith("-pass") || (!arg2.endsWith("-fail") && Math.random()<0.5)) {
console.log("complete:",arg1,arg2);
clearTimeout(timer);
resolve([arg1,arg2,"all good"].join("-"));
}
},2000);
});}
function sampleFunc4(arg1,arg2) {return asCallback(arguments,function(cb){
console.log("deciding:",arg1,arg2);
const timer = setTimeout(function(){cb([arg1,arg2,"ouch"].join("-"));},5000);
setTimeout(function(){
if (arg2.endsWith("-pass") || (!arg2.endsWith("-fail") && Math.random()<0.5)) {
console.log("complete:",arg1,arg2);
clearTimeout(timer);
cb(undefined,[arg1,arg2,"all good"].join("-"));
}
},2000);
});}
тестовый сценарий для вышеуказанных функций
const local = {};
promiseToCB(function sampleFunc1(arg1,arg2) {
console.log("deciding:",arg1,arg2);
return new Promise(function(resolve,reject){
const timer = setTimeout(function(){reject([arg1,arg2,"ouch"].join("-"));},5000);
setTimeout(function(){
if (arg2.endsWith("-pass") || (!arg2.endsWith("-fail") && Math.random()<0.5)) {
console.log("complete:",arg1,arg2);
clearTimeout(timer);
resolve([arg1,arg2,"all good"].join("-"));
}
},2000);
});
});
cbToPromise('sampleFunc2',function someOtherName(arg1,arg2,cb) {
console.log("deciding:",arg1,arg2);
const timer = setTimeout(function(){cb([arg1,arg2,"ouch"].join("-"));},5000);
setTimeout(function(){
if (arg2.endsWith("-pass") || (!arg2.endsWith("-fail") && Math.random()<0.5)) {
console.log("complete:",arg1,arg2);
clearTimeout(timer);
cb(undefined,[arg1,arg2,"all good"].join("-"));
}
},2000);
},local);
function sampleFunc3(arg1,arg2) {return asPromise(arguments,function(resolve,reject){
console.log("deciding:",arg1,arg2);
const timer = setTimeout(function(){reject([arg1,arg2,"ouch"].join("-"));},5000);
setTimeout(function(){
if (arg2.endsWith("-pass") || (!arg2.endsWith("-fail") && Math.random()<0.5)) {
console.log("complete:",arg1,arg2);
clearTimeout(timer);
resolve([arg1,arg2,"all good"].join("-"));
}
},2000);
});}
function sampleFunc4(arg1,arg2) {return asCallback(arguments,function(cb){
console.log("deciding:",arg1,arg2);
const timer = setTimeout(function(){cb([arg1,arg2,"ouch"].join("-"));},5000);
setTimeout(function(){
if (arg2.endsWith("-pass") || (!arg2.endsWith("-fail") && Math.random()<0.5)) {
console.log("complete:",arg1,arg2);
clearTimeout(timer);
cb(undefined,[arg1,arg2,"all good"].join("-"));
}
},2000);
});}
const log=console.log.bind(console),info=console.info.bind(console),error=console.error.bind(console);
sampleFunc1("sample1","promise").then (log).catch(error);
local.sampleFunc2("sample2","promise").then (log).catch(error);
sampleFunc3("sample3","promise").then (log).catch(error);
sampleFunc4("sample4","promise").then (log).catch(error);
sampleFunc1("sample1","callback",info);
local.sampleFunc2("sample2","callback",info);
sampleFunc3("sample3","callback",info);
sampleFunc4("sample4","callback",info);
sampleFunc1("sample1","promise-pass").then (log).catch(error);
local.sampleFunc2("sample2","promise-pass").then (log).catch(error);
sampleFunc3("sample3","promise-pass").then (log).catch(error);
sampleFunc4("sample4","promise-pass").then (log).catch(error);
sampleFunc1("sample1","callback-pass",info);
local.sampleFunc2("sample2","callback-pass",info);
sampleFunc3("sample3","callback-pass",info);
sampleFunc4("sample4","callback-pass",info);
sampleFunc1("sample1","promise-fail").then (log).catch(error);
local.sampleFunc2("sample2","promise-fail").then (log).catch(error);
sampleFunc3("sample3","promise-fail").then (log).catch(error);
sampleFunc4("sample4","promise-fail").then (log).catch(error);
sampleFunc1("sample1","callback-fail",info);
local.sampleFunc2("sample2","callback-fail",info);
sampleFunc3("sample3","callback-fail",info);
sampleFunc4("sample4","callback-fail",info);
поскольку мы заранее знаем характеристики функции, основанной на обратном вызове, мы можем создать функцию, которая преобразует функцию, основанную на обратном вызове, в эквивалентную функцию, возвращающую обещание.
Обратный вызов — это последний аргумент функции.
Если есть ошибка, это всегда первый аргумент, передаваемый обратному вызову.
Любое возвращаемое значение передается после ошибки обратному вызову
function promisify(yourCallbackApi) { return function promisified(...args) { return new Promise((resolve, reject) => { // newArgs=[..args,callback] const newArgs = [ ...args, function (err, result) { if (err) { return reject(err); } resolve(result); }, ]; // invoke yourCallbackApi with the new list of arguments yourCallbackApi(...newArgs); }); }; }
Возможно, уже ответил, но я обычно это делаю так:
// given you've defined this `Future` fn somewhere:
const Future = fn => {return new Promise((r,t) => fn(r,t))}
// define an eventFn that takes a promise `resolver`
const eventFn = resolve => {
// do event related closure actions here. When finally done, call `resolve()`
something.oneventfired = e => {resolve(e)}
}
// invoke eventFn in an `async` workflowFn using `Future`
// to obtain a `promise` wrapper
const workflowFn = async () => {await Future(eventFn)}
Особенно для таких вещей, как
indexedDb
оболочки событий для упрощения использования.
Или вы можете найти этот вариант
Future
быть более универсальным
class PromiseEx extends Promise {
resolve(v,...a) {
this.settled = true; this.settledValue = v;
return(this.resolve_(v,...a))
}
reject(v,...a) {
this.settled = false; this.settledValue = v;
return(this.reject_(v,...a))
}
static Future(fn,...args) {
let r,t,ft = new PromiseEx((r_,t_) => {r=r_;t=t_})
ft.resolve_ = r; ft.reject_ = t; fn(ft,...args);
return(ft)
}
}
Я создал универсальную функцию, которую я могу использовать повторно.
const wrapIntoPromise: (fnToExecute) => {
return new Promise((resolve, reject) => {
return fnToExecute(resolve, reject);
});
}
//example
async function run() {
await wrapIntoPromise((resolve, reject) => {
/*execute any function here with callbacks etc
when done just call return resolve(valueToReturn)
*/
return backup({
callback: function() { return resolve()}
});
});
}
Вы можете использовать пакет callback2Promise npm для преобразования функций стиля узла в Promises.
var c2p = require('callback2promise');
// ordinary function with any number of parameters and a callback at the end
var nodeStyleFunc = function(param1, param2, callback){
setTimeout(
function(){
callback(null, 'done')
}, 200);
}
// convert the function to a promise
var promise = c2p(nodeStyleFunc)(param1, param2);
promise
.then(result => console.log(result))
.catch(err => console.log(err));
Это на 5 лет позже, но я хотел опубликовать здесь мою версию с промыслами, которая берет функции из API обратных вызовов и превращает их в обещания
const promesify = fn => {
return (...params) => ({
then: cbThen => ({
catch: cbCatch => {
fn(...params, cbThen, cbCatch);
}
})
});
};
Посмотрите на эту очень простую версию здесь: https://gist.github.com/jdtorregrosas/aeee96dd07558a5d18db1ff02f31e21a