首页 文章

如何将现有回调API转换为承诺?

提问于
浏览
581

我想使用promises,但我有一个回调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) {
            ...
        });
    });
});

如何在promises中使用API,我该如何“宣传”它?

17 回答

  • 599

    kriskowal的Q库包括回调承诺函数 . 像这样的方法:

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

    可以用Q.ninvoke转换

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

    你可以做这样的事情

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

    今天,我可以在 Node.js 中使用 Promise 作为普通的Javascript方法 .

    一个简单的基本示例 Promise (使用 KISS 方式):

    Plain Javascript异步API代码:

    function divisionAPI (number, divider, successCallback, errorCallback) {
    
        if (divider == 0) {
            return errorCallback( new Error("Division by zero") )
        }
    
        successCallback( number / divider )
    
    }
    

    Promise Javascript异步API代码:

    function divisionAPI (number, divider) {
    
        return new Promise(function (fulfilled, rejected) {
    
            if (divider == 0) {
                return rejected( new Error("Division by zero") )
            }
    
            fulfilled( number / divider )
    
         })
    
    }
    

    (我建议访问this beautiful source

    Promise 也可以与 ES7 中的 async\await 一起使用,以使程序流等待 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 .

    希望这可以帮助 .

  • 21

    使用简单的旧vanilla 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);
    });
    
  • 0

    在Node.js 8.0.0的候选版本中,有一个新的实用程序, util.promisify (我写的是util.promisify),它封装了宣传任何函数的能力 .

    它与其他答案中提出的方法没有太大的不同,但具有作为核心方法的优点,并且不需要额外的依赖性 .

    const fs = require('fs');
    const util = require('util');
    
    const readFile = util.promisify(fs.readFile);
    

    然后你有一个返回原生 PromisereadFile 方法 .

    readFile('./notes.txt')
      .then(txt => console.log(txt))
      .catch(...);
    
  • 5

    在节点v7.6下,它内置了promises和async:

    // 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;
    }
    
  • 10

    您可以在Node JS中使用JavaScript本机承诺 .

    My 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
    
  • 3

    es6-promisify 将基于回调的函数转换为基于Promise的函数 .

    const promisify = require('es6-promisify');
    
    const promisedFn = promisify(callbackedFn, args);
    

    参考:https://www.npmjs.com/package/es6-promisify

  • 2

    我不认为@Benjamin的 window.onload 建议会一直有效,因为它不会检测它是否在加载后被调用 . 我被这多次咬过了 . 这是一个应该始终有效的版本:

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

    当你有一些函数需要回调并且你希望它们返回一个promise而你可以使用这个函数来进行转换 .

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

    在Node.js 8中,您可以使用此npm模块动态地使用对象方法:

    https://www.npmjs.com/package/doasync

    它使用 util.promisifyProxies ,以便您的对象保持不变 . Memoization 也是使用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);
        // ...
      });
    

    您甚至可以使用原生 callapply 来绑定某些上下文:

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

    承诺有状态,他们开始等待,并可以解决:

    • fulfilled 表示计算成功完成 .

    • rejected 表示计算失败 .

    承诺返回函数should never throw,他们应该返回拒绝 . 从promise返回函数中抛出将强制您同时使用 } catch {.catch . 使用promisified API的人不希望承诺投掷 . 如果您不确定JS中的异步API是如何工作的 - 请首先see this answer .

    1. DOM加载或其他一次性事件:

    因此,创建承诺通常意味着指定它们何时结算 - 这意味着它们何时转移到已完成或被拒绝的阶段以指示数据可用(并且可以使用 .then 进行访问) .

    使用支持 Promise 构造函数的现代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;
    }
    

    或者使用类似API的jQuery,挂钩一次发生的事件:

    function done() {
        var d = $.Deferred();
        $("#myObject").once("click",function() {
            d.resolve();
        });
        return d.promise();
    }
    

    2.普通回调:

    这些API很常见,因为...回调在JS中很常见 . 让我们来看看 onSuccessonFail 的常见情况:

    function getUserData(userId, onLoad, onFail) { …
    

    使用支持 Promise 构造函数的现代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延迟的 resolvereject 方法是"detachable";即 . 它们绑定到jQuery.Deferred()的实例 . 并非所有的lib都提供此功能 .

    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现在支持新语法which you should prefer):

    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.具有节点样式回调的整个库:

    这里没有黄金法则,你一个接一个地宣传它们 . 但是,一些promise实现允许您批量执行此操作,例如在Bluebird中,将nodeback API转换为promise 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 处理程序返回一个promise将解析或拒绝该promise的值 . 从一个 .then 处理程序投掷也是很好的做法,并将拒绝承诺 - 这是着名的承诺抛出安全 .

    • 在实际的 onload 情况下,您应该使用 addEventListener 而不是 onX .

  • 2

    callback style 函数总是这样(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是一个优秀的函数式编程库 . 在上面的代码中,我们使用它的 apply (如javascript function.prototype.apply )和追加(如javascript function.prototype.push ) . 所以,我们现在可以将回调样式函数转换为promise样式函数:

    const {readdir} = require('fs')
    const readdirP = toPromise(readdir)
    readdir(Path)
        .then(
            (files) => console.log(files),
            (err) => console.log(err)
        )
    

    toPromisecheckErr 函数由berserk库拥有,它是 ramda.js (由我创建)的函数式编程库 .

    希望这个答案对你有用 .

  • 0

    我的 callback 函数的promisify版本是 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) .

  • 5

    Node.js 8.0.0包含一个新的 util.promisify() API,它允许将标准Node.js回调样式API包装在一个返回Promise的函数中 . util.promisify() 的示例用法如下所示 .

    const fs = require('fs');
    const util = require('util');
    
    const readfile = util.promisify(fs.readFile);
    
    readfile('/some/file')
      .then((data) => { /** ... **/ })
      .catch((err) => { /** ... **/ });
    

    Improved support for Promises

  • 0

    您可以在ES6中使用 native Promise ,例如处理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
        });
    
    }
    

    在这个例子中,Promise没有理由失败,所以 reject() 永远不会被调用 .

  • 2

    Before converting a function as promise In 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)
    })
    

    After Converting It

    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 you need to handle multiple request

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

相关问题