Як перетворити існуючий API зворотного виклику в обіцянки?


721

Я хочу працювати з обіцянками, але у мене є API зворотного виклику у такому форматі:

1. Завантаження DOM або інша разова подія:

window.onload; // set to callback
...
window.onload = function() {

};

2. Простий зворотний виклик:

function request(onChangeHandler) {
    ...
}
request(function() {
    // change happened
    ...
});

3. Зворотний виклик стилю вузла ("зворотний зв'язок"):

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 Цікава ідея, я спробував зробити загальну відповідь, що використовує два загальні підходи (конструктор обіцянок та відкладений об’єкт). Я намагався дати відповіді на два варіанти. Я погоджуюся, що RTFMing вирішує цю проблему, але ми часто стикаємося з цією проблемою як тут, так і в трекері помилок, тому я зрозумів, що існує "канонічне питання" - я думаю, що RTFMing вирішує близько 50% питань у тезі JS: D Якщо у вас є цікаве розуміння, щоб внести відповідь у відповідь чи відредагувати, це буде дуже вдячно.
Бенджамін Груенбаум

Чи створює new Promiseдодаток якісь значні накладні витрати? Я хочу обернути всі свої синхронні функції Noje.js в Обіцянні, щоб видалити весь синхронний код з мого додатка Node, але це найкраща практика? Іншими словами, функцію, яка приймає статичний аргумент (наприклад, рядок) і повертає обчислений результат, я повинен обернути це в обіцянку? ... Я десь прочитав, що у вас не повинно бути жодного синхронного коду в Nodejs.
Ронні Ройстон

1
@RonRoyston ні, це не гарна ідея обертати синхронні дзвінки обіцянками - лише асинхронні дзвінки, які можуть виконувати введення-виведення
Бенджамін

Відповіді:


743

Обіцяння є державними, вони починаються як очікувані і можуть вирішуватись на:

  • Виконано значення, що обчислення завершено успішно.
  • відкинув значення, що обчислення не вдалося.

Обіцяючі функції повернення ніколи не повинні кидати , вони повинні повертати відхилення. Викидання функції повернення обіцянки змусить вас використовувати і 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 resolveі rejectметоди "відривні"; тобто. вони пов'язані з екземпляром jQuery.Deferred (). Не всі гігієни пропонують цю функцію.

3. Зворотний виклик стилю вузла ("зворотний зв'язок"):

Зворотні виклики стилю вузла (вузли зворотного зв'язку) мають певний формат, де зворотні виклики завжди є останнім аргументом, а його першим параметром є помилка. Давайте спочатку обережно позначимо один:

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

За допомогою відкладених даних ви можете зробити наступне (давайте використовувати 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. До того, як він з’явиться, йому знадобиться експертна перевірка. Сподіваюся, вам сподобається.
Roamer-1888,

@ Roamer-1888 його відхилили, оскільки я його вчасно не побачив і не прийняв. Я не думаю, що додаток є надто актуальним, хоча й корисним.
Бенджамін Грюнбаум

2
Benjamin, незалежно від того , чи ні , resolve()і reject()написано , щоб бути багаторазовими, я ризикую , що мої запропонували змінити це значення , оскільки він пропонує приклад JQuery форми $.Deferred(fn), яка в іншому випадку відсутній. Якщо включений лише один приклад jQuery, то я пропоную, що він повинен бути такої форми, а не var d = $.Deferred();тощо, оскільки людям слід заохочувати використовувати часто занедбану $.Deferred(fn)форму, плюс, у відповіді, як це, вона ставить jQuery більше нарівні з гібриди, які використовують шаблон розкриття конструктора .
Roamer-1888,

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

7
Це чудова відповідь. Ви можете оновити його, зазначивши також util.promisify, що Node.js збирається додати до свого ядра, починаючи з RC 8.0.0. Його робота не сильно відрізняється від Bluebird Promise.promisify, але має перевагу в тому, що не потребує додаткових залежностей, якщо ви просто хочете рідного Обіцяння. Я написав повідомлення в блозі про util.promisify для всіх, хто хоче детальніше прочитати тему.
Бруно

55

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

Простий та основний приклад Promise(зі способом KISS ):

Простий код API Async Javascript:

function divisionAPI (number, divider, successCallback, errorCallback) {

    if (divider == 0) {
        return errorCallback( new Error("Division by zero") )
    }

    successCallback( number / divider )

}

Promise Код API Jayy Async:

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

Якщо вам потрібно обробити кілька запитів

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 обернутись у функції, яка повертає Обіцяння. Приклад використання 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
Просто тому, що версія версії вузла тепер випущена, і я повідомив про "офіційний" опис функції та посилання.
Gian Marco Gherardi

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
Гей, я (ОП) насправді запропонував util.promisifyдвічі (ще в 2014 році, коли це питання було написано, і кілька місяців тому - що я виступав за основний член Node і є поточною версією, яку ми маємо в Node). Оскільки він ще не є загальнодоступним - я ще не додав його до цієї відповіді. Ми б дуже вдячні за відгуки про використання, але і дізнаємось, які існують деякі підводні камені, щоб мати кращі документи для випуску :)
Бенджамін Груенбаум

1
Крім того, ви можете обговорити спеціальний прапор для промальовування util.promisifyв своєму блозі :)
Benjamin Gruenbaum

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

1
Зрозуміло, врахуйте такі API, fs.existsчи API, які не дотримуються конвенції Node - блакитна пташка помилиться з Promise.promisify ними, але зробить util.promisifyїх правильно.
Бенджамін Груенбаум

7

За допомогою Node JS ви можете використовувати власні обіцянки JavaScript.

Моє посилання на Cloud Cloud 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
Я вважаю це корисним як Google для того, щоб провести багатозначні виводи у сюди,
Ед Сайкс

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

Ви навіть можете використовувати рідну мову 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 - відмінна бібліотека для функціонального програмування. У наведеному вище коді ми використовували це застосовувати (як 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 (створити мною).

Сподіваюсь, ця відповідь корисна для вас.


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
Гей, я не впевнений, що це додає до існуючих відповідей (можливо, уточнити?). Крім того, немає потреби в спробі / лові всередині конструктора обіцянок (він робить це автоматично для вас). Незрозуміло також, для яких функцій це працює (що викликає зворотний виклик з єдиним аргументом щодо успіху? Як обробляються помилки?)
Бенджамін Груенбаум


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
Яка перевага у цьому є перед назвою або над відповідями вище?
Бенджамін Груенбаум

Що ви маєте на увазі для рідного обіцяння?
loretoparisi


ах, звичайно :). Просто і на прикладі, щоб показати основну ідею. Насправді ви можете бачити, як навіть (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.