Как преобразовать существующий API обратного вызова в обещания?


721

Я хочу работать с обещаниями, но у меня есть 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 в обещаниях, как его «обещать»?


Я опубликовал свой собственный ответ, но ответы на вопросы о том, как это сделать для конкретной библиотеки или в более сложных обстоятельствах, также очень приветствуются.
— Бенджамин Грюнбаум

@Bergi Это интересная идея, я попытался дать общий ответ, который использует два общих подхода (конструктор Promise и отложенный объект). Я попытался дать две альтернативы в ответах. Я согласен, что RTFMing решает эту проблему, но мы часто сталкиваемся с этой проблемой и здесь, и в трекере ошибок, поэтому я решил, что существует «канонический вопрос» - я думаю, что RTFMing решает около 50% проблем в теге JS: D Если У вас есть интересное понимание, чтобы внести свой вклад в ответ или отредактировать его, и мы будем очень признательны.
— Бенджамин Грюнбаум

Создает ли new Promiseдобавление какие-либо существенные накладные расходы? Я хочу обернуть все мои синхронные функции Noje.js в Promise, чтобы удалить весь синхронный код из моего Node-приложения, но так ли это лучше? Другими словами, функция, которая принимает статический аргумент (например, строку) и возвращает вычисленный результат, я должен обернуть это в обещание? ... Я где-то читал, что у вас не должно быть никакого синхронного кода в Nodejs.
— Ронни Ройстон

1
@RonRoyston нет, не стоит переносить синхронные вызовы с обещаниями - только асинхронные вызовы, которые могут выполнять ввод / вывод
— Бенджамин Грюнбаум

Ответы:


744

Обещания имеют статус, они начинаются как ожидающие и могут рассчитывать на:

  • выполнив это означает, что вычисление завершено успешно.
  • отклонено означает, что вычисление не удалось.

Функции возврата обещаний никогда не должны выдаваться, вместо этого они должны возвращать отклонения. Функция возврата из обещания заставит вас использовать и a, } catch { и a .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 deferred resolveи rejectметоды являются «отделяемыми»; то есть. они привязаны к экземпляру jQuery.Deferred (). Не все библиотеки предлагают эту функцию.

3. Обратный вызов стиля узла («nodeback»):

Обратные вызовы стиля узла (nodebacks) имеют определенный формат, где обратные вызовы всегда являются последним аргументом, а его первый параметр является ошибкой. Давайте сначала пообещаем одно вручную:

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 обратной ноды в API обещаний так же просто, как:

Promise.promisifyAll(API);

Или с нативными обещаниями в Node :

const { promisify } = require('util');
const promiseAPI = Object.entries(API).map(([key, v]) => ({key, fn: promisify(v)}))
                         .reduce((o, p) => Object.assign(o, {[p.key]: p.fn}), {});

Ноты:

  • Конечно, когда вы находитесь в .thenобработчике, вам не нужно ничего обещать. Возврат обещания от .thenобработчика разрешит или отклонит значение этого обещания. Бросок из .thenобработчика также является хорошей практикой и отвергнет обещание - это знаменитое обещание безопасности броска.
  • В реальном onloadслучае вы должны использовать, addEventListenerа не onX.

Бенджамин, я принял твое приглашение отредактировать и добавил еще один пример jQuery в дело 2. Прежде чем он появится, он должен быть рецензирован. Надеюсь, вам понравится.
— Roamer-1888

@ Roamer-1888 это было отклонено, так как я не видел и не принимал это вовремя. Что бы это ни стоило, я не думаю, что дополнение слишком уместно, хотя и полезно.
— Бенджамин Грюнбаум

2
Benjamin, независимо от того , или нет , resolve()и reject()написано , чтобы быть многоразовыми, я рискую , что мои предложили изменить это значение , поскольку он предлагает пример JQuery формы $.Deferred(fn), которая в противном случае отсутствует. Если включен только один пример jQuery, то я предлагаю, чтобы он имел такую ​​форму, а не var d = $.Deferred();и т. Д., Поскольку людям следует поощрять использование часто пренебрегаемой $.Deferred(fn)формы, плюс в ответе, подобном этому, он ставит jQuery в один ряд с libs, которые используют шаблон Revealing Constructor .
— Roamer-1888

Хех, чтобы быть на 100% справедливым, я не знал, что jQuery позволит вам это сделать $.Deferred(fn), если вы отредактируете это вместо существующего примера в течение следующих 15 минут, я уверен, что могу попытаться утвердить его вовремя :)
— Бенджамин Грюнбаум,

7
Это отличный ответ. Вы можете обновить его, упомянув также util.promisify, что Node.js собирается добавить в свое ядро ​​начиная с RC 8.0.0. Его работа не сильно отличается от Bluebird Promise.promisify, но имеет то преимущество, что не требует дополнительных зависимостей, в случае, если вы просто хотите родной Promise. Я написал сообщение в блоге об util.promisify для тех, кто хочет узнать больше на эту тему.
— Бруно,

55

Сегодня я могу использовать Promiseв Node.jsкачестве простого метода Javascript.

Простой и простой пример Promise(с KISS way):

Простой 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.

Бонус : гибридный метод
(предполагается, что метод обратного вызова имеет два параметра как ошибку и результат)

function divisionAPI (number, divider, callback) {

    return new Promise(function (fulfilled, rejected) {

        if (divider == 0) {
            let error = new Error("Division by zero")
            callback && callback( error )
            return rejected( error )
        }

        let result = number / divider
        callback && callback( null, result )
        fulfilled( result )

     })

}

Вышеупомянутый метод может ответить на результат для старого способа обратного вызова и обещаний использования.

Надеюсь это поможет.


3
Похоже, они не показывают, как перейти к обещаниям.
— Дмитрий Зайцев

33

Перед преобразованием функции в качестве обещания в 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');

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)
});

23

Я не думаю, что window.onloadпредложение @Benjamin будет работать постоянно, так как оно не определяет, вызывается ли оно после загрузки. Я был укушен этим много раз. Вот версия, которая всегда должна работать:

function promiseDOMready() {
    return new Promise(function(resolve) {
        if (document.readyState === "complete") return resolve();
        document.addEventListener("DOMContentLoaded", resolve);
    });
}
promiseDOMready().then(initOnLoad);

1
не должна ли "уже завершенная" ветвь использовать setTimeout(resolve, 0)(или setImmediate, если возможно) ветку , чтобы она вызывалась асинхронно?
— Альнитак

5
@Alnitak Синхронный вызов resolveв порядке. thenОбработчики Promise гарантируются асинхронным вызовом платформы независимо от того, resolveвызывается ли синхронно.
— Джефф Боуман

15

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) => { /** ... **/ });

См. Улучшенная поддержка для обещаний


2
Уже есть два ответа, описывающих это, зачем размещать третий?
— Бенджамин Грюнбаум

1
Просто потому, что эта версия узла уже выпущена, и я сообщил «официальное» описание функции и ссылку.
— Джан Марко Герарди

14

В выпуске кандидата на 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(...);

1
Привет, я (OP) фактически предложил util.promisifyдважды (еще в 2014 году, когда этот вопрос был написан, и несколько месяцев назад - который я настаивал на том, чтобы стать основным членом Node, и это текущая версия, которую мы имеем в Node). Поскольку он еще не является общедоступным - я еще не добавил его к этому ответу. Мы были бы очень признательны за отзывы об использовании и знакомство с некоторыми подводными камнями, чтобы иметь лучшие документы для релиза :)
— Бенджамин Грюнбаум

1
Кроме того, вы можете обсудить пользовательский флаг для обещания util.promisifyв своем блоге :)
— Бенджамин Грюнбаум

@BenjaminGruenbaum Вы имеете в виду тот факт, что с помощью util.promisify.customсимвола можно переопределить результат util.promisify? Честно говоря, это было преднамеренное промах, потому что я пока не могу найти полезного варианта использования. Возможно, вы можете дать мне некоторые входные данные?
— Бруно,

1
Конечно, рассмотрим API-интерфейсы, такие как fs.existsили API-интерфейсы, которые не следуют соглашению Node - синяя птица Promise.promisify может ошибиться, но сделает util.promisifyэто правильно.
— Бенджамин Грюнбаум

7

Вы можете использовать собственные обещания 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

7

С простым старым ванильным 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);
});

6

Библиотека Q от kriskowal включает функции обратного вызова к обещанию. Такой метод:

obj.prototype.dosomething(params, cb) {
  ...blah blah...
  cb(error, results);
}

можно конвертировать с помощью Q.ninvoke

Q.ninvoke(obj,"dosomething",params).
then(function(results) {
});

1
В каноническом ответе уже упоминается Q.denodeify. Нужно ли выделять библиотечных помощников?
— Берги

3
я нашел это полезным, как гугл об обещании в Q приводит здесь
— Эд Сайкс

4

Если у вас есть несколько функций, которые принимают обратный вызов, и вы хотите, чтобы они вместо этого возвращали обещание, вы можете использовать эту функцию для выполнения преобразования.

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;
    }
}

4

Под узлом 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;
}

3

В 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);
    // ...
  });

Вы даже можете использовать native callи applyдля привязки некоторого контекста:

doAsync(myFunc).apply(context, params)
  .then(result => { /*...*/ });

2

Вы можете использовать встроенный 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()никогда не вызывается.


2

Функция стиля обратного вызова всегда такая (почти все функции в node.js - это стиль):

//fs.readdir(path[, options], callback)
fs.readdir('mypath',(err,files)=>console.log(files))

Этот стиль имеет ту же особенность:

  1. функция обратного вызова передается последним аргументом.

  2. функция обратного вызова всегда принимает объект ошибки в качестве первого аргумента.

Таким образом, вы можете написать функцию для преобразования функции с этим стилем следующим образом:

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 - отличная библиотека для функционального программирования. В приведенном выше коде мы использовали его apply (как javascript function.prototype.apply) и append (как 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 ( созданная мной).

Надеюсь, что этот ответ полезен для вас.


2

Вы можете сделать что-то вроде этого

// @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)
}

2
Эй, я не уверен, что это добавляет к существующим ответам (возможно, уточнить?). Кроме того, нет необходимости в try / catch внутри конструктора обещаний (он делает это автоматически для вас). Также неясно, для каких функций это работает (которые вызывают обратный вызов с одним аргументом об успехе? Как обрабатываются ошибки?)
— Бенджамин Грюнбаум


1

Моя многообещающая версия 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).


1
Какое преимущество это имеет над нативным обещанием или над ответами выше?
— Бенджамин Грюнбаум

Что вы имеете в виду для родного обещания?
— Лоретопариси


ах да конечно :). Как раз и пример, чтобы показать основную идею. На самом деле вы можете видеть, как даже нативный требует, чтобы сигнатура функции была определена как, (err, value) => ...или вы должны определить собственную (см. Пользовательские обещанные функции). Спасибо, хороший кальян.
— Loretoparisi

1
@loretoparisi FYI, var P = function (fn, ...args) { return new Promise((resolve, reject) => fn.call(this, ...args, (error, result) => error ? reject(error) : resolve(result))); };сделал бы то же самое, что и вы, и это намного проще.
— Патрик Робертс

1

Ниже приведена реализация того, как функция (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');

-2

Это на 5 лет позже, но я хотел опубликовать здесь мою версию с промыслами, которая берет функции из API обратных вызовов и превращает их в обещания

const promesify = fn => {
  return (...params) => ({
    then: cbThen => ({
      catch: cbCatch => {
        fn(...params, cbThen, cbCatch);
      }
    })
  });
};

Взгляните на эту очень простую версию здесь: https://gist.github.com/jdtorregrosas/aeee96dd07558a5d18db1ff02f31e21a


1
Это не обещание, оно не
— Бенджамин Грюнбаум
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.