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

Ben*_*aum 680 javascript callback node.js promise bluebird

我想使用promises,但我有一个回调API,格式如下:

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

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

};
Run Code Online (Sandbox Code Playgroud)

2.平原回调:

function request(onChangeHandler) {
    ...
}
request(function() {
    // change happened
    ...
});
Run Code Online (Sandbox Code Playgroud)

3.节点样式回调("nodeback"):

function getStuff(dat, callback) {
    ...
}
getStuff("dataParam", function(err, data) {
    ...
})
Run Code Online (Sandbox Code Playgroud)

4.具有节点样式回调的整个库:

API;
API.one(function(err, data) {
    API.two(function(err, data2) {
        API.three(function(err, data3) {
            ...
        });
    });
});
Run Code Online (Sandbox Code Playgroud)

如何在promises中使用API​​,我该如何"宣传"它?

Ben*_*aum 704

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

  • 实现了计算成功完成的意义.
  • 拒绝意味着计算失败.

承诺返回函数永远不应该抛出,它们应该返回拒绝.从承诺返回函数中抛出将强制您同时使用a } catch { a .catch.使用promisified API的人不希望承诺投掷.如果您不确定异步API在JS中是如何工作的 - 请先查看此答案.

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

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

使用现代承诺实现支持Promise构造函数,如本机ES6承诺:

function load() {
    return new Promise(function(resolve, reject) {
        window.onload = resolve;
    });
}
Run Code Online (Sandbox Code Playgroud)

然后,您将使用生成的承诺,如下所示:

load().then(function() {
    // Do things after onload
});
Run Code Online (Sandbox Code Playgroud)

使用支持延迟的库(让我们在这里使用$ q这个例子,但我们稍后也会使用jQuery):

function load() {
    var d = $q.defer();
    window.onload = function() { d.resolve(); };
    return d.promise;
}
Run Code Online (Sandbox Code Playgroud)

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

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

2.平原回调:

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

function getUserData(userId, onLoad, onFail) { …
Run Code Online (Sandbox Code Playgroud)

使用现代承诺实现支持Promise构造函数,如本机ES6承诺:

function getUserDataAsync(userId) {
    return new Promise(function(resolve, reject) {
        getUserData(userId, resolve, reject);
    });
}
Run Code Online (Sandbox Code Playgroud)

使用支持延迟的库(我们在这里使用jQuery这个例子,但我们也使用了上面的$ q):

function getUserDataAsync(userId) {
    var d = $.Deferred();
    getUserData(userId, function(res){ d.resolve(res); }, function(err){ d.reject(err); });
    return d.promise();
}
Run Code Online (Sandbox Code Playgroud)

jQuery还提供了一个$.Deferred(fn)表单,它的优点是允许我们编写一个非常接近new Promise(fn)表单的表达式,如下所示:

function getUserDataAsync(userId) {
    return $.Deferred(function(dfrd) {
        getUserData(userId, dfrd.resolve, dfrd.reject);
    }).promise();
}
Run Code Online (Sandbox Code Playgroud)

注意:这里我们利用jQuery延迟resolvereject方法是"可拆卸" 的事实; 即.它们绑定到jQuery.Deferred()的实例.并非所有的lib都提供此功能.

3.节点样式回调("nodeback"):

节点样式回调(nodebacks)具有特定格式,其中回调始终是最后一个参数,其第一个参数是错误.让我们首先手动宣传一个:

getStuff("dataParam", function(err, data) { …
Run Code Online (Sandbox Code Playgroud)

至:

function getStuffAsync(param) {
    return new Promise(function(resolve, reject) {
        getStuff(param, function(err, data) {
            if (err !== null) reject(err);
            else resolve(data);
        });
    });
}
Run Code Online (Sandbox Code Playgroud)

使用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;   
}
Run Code Online (Sandbox Code Playgroud)

一般来说,你不应该手动过多地宣传一些东西,大多数承诺在设计时考虑到Node以及Node 8+中的本机承诺的库都有内置的方法来实现节点回发.例如

var getStuffAsync = Promise.promisify(getStuff); // Bluebird
var getStuffAsync = Q.denodeify(getStuff); // Q
var getStuffAsync = util.promisify(getStuff); // Native promises, node only
Run Code Online (Sandbox Code Playgroud)

4.具有节点样式回调的整个库:

这里没有黄金法则,你一个接一个地宣传它们.但是,一些promise实现允许您批量执行此操作,例如在Bluebird中,将nodeback API转换为promise API非常简单:

Promise.promisifyAll(API);
Run Code Online (Sandbox Code Playgroud)

或与原生承诺节点:

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}), {});
Run Code Online (Sandbox Code Playgroud)

笔记:

  • 当然,当你在.then处理程序中时,你不需要宣传事物.从.then处理程序返回承诺将使用该承诺的值来解决或拒绝.从.then处理程序中投掷也是很好的做法,并且会拒绝承诺 - 这是着名的承诺投掷安全.
  • 在实际onload情况中,您应该使用addEventListener而不是onX.

  • 这是一个很好的答案.你可能想要通过提及`util.promisify`来更新它,Node.js将从RC 8.0.0开始添加到它的核心.它的工作方式与Bluebird的"Promise.promisify"没什么不同,但是如果你只想要原生的Promise,它的优点是不需要额外的依赖.我已经为任何想要阅读更多相关主题的人撰写了关于[util.promisify](https://brunoscopelliti.com/new-util-promisify-in-nodejs/)的博客文章. (7认同)
  • Benjamin,无论 `resolve()` 和 `reject()` 是否被编写为可重用,我敢说我建议的编辑是相关的,因为它提供了一个表单 `$.Deferred(fn)` 的 jQuery 示例,它是否则缺乏。如果只包含一个 jQuery 示例,那么我建议它应该是这种形式而不是 `var d = $.Deferred();` 等,因为应该鼓励人们使用经常被忽视的 `$.Deferred(fn) ` 形式,另外,在这样的答案中,它使 jQuery 与使用 [Revealing Constructor Pattern](http://domenic.me) 的库更加相提并论。 (2认同)

efk*_*kan 50

今天,我可以用PromiseNode.js作为一个普通的JavaScript方法.

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

普通的 Javascript异步API代码:

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

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

    successCallback( number / divider )

}
Run Code Online (Sandbox Code Playgroud)

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 )

     })

}
Run Code Online (Sandbox Code Playgroud)

(我建议访问这个美丽的来源)

Promise可以一起使用async\awaitES7,以使程序流程等待一个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
Run Code Online (Sandbox Code Playgroud)

使用.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) })
Run Code Online (Sandbox Code Playgroud)

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 )

     })

}
Run Code Online (Sandbox Code Playgroud)

上述方法可以响应旧时尚回调和Promise使用的结果.

希望这可以帮助.

  • 这些似乎并没有显示如何转换为承诺. (3认同)

Siv*_*nan 30

在Node.JS中将函数转换为promise之前

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)
})
Run Code Online (Sandbox Code Playgroud)

转换后

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)
})
Run Code Online (Sandbox Code Playgroud)

如果你需要处理多个请求

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)
});
Run Code Online (Sandbox Code Playgroud)


Leo*_*Leo 22

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

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

  • @Alnitak同步调用`resolve`很好.Promise的`then`处理程序[由异步调用的框架保证](http://stackoverflow.com/a/38062501/1426891),无论是否同步调用`resolve`. (5认同)

Jos*_*iah 15

来自未来

我通常使用的一个简单的通用函数。

const promisify = (fn, ...args) => {
  return new Promise((resolve, reject) => {
    fn(...args, (err, data) => {
      if (err) {
        return reject(err);
      }
      resolve(data);
    });
  });
};
Run Code Online (Sandbox Code Playgroud)

如何使用它

   promisify(fn, arg1, arg2)
Run Code Online (Sandbox Code Playgroud)

您可能不是在寻找这个答案,但这将有助于了解可用工具的内部工作原理


Gia*_*rdi 14

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) => { /** ... **/ });
Run Code Online (Sandbox Code Playgroud)

请参阅改进的Promise支持

  • 已有两个答案描述这个,为什么发布第三个? (2认同)
  • 仅仅因为该版本的节点现已发布,并且我已经报告了“官方”功能描述和链接。 (2认同)

Bru*_*uno 13

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

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

const fs = require('fs');
const util = require('util');

const readFile = util.promisify(fs.readFile);
Run Code Online (Sandbox Code Playgroud)

然后你有一个readFile返回原生的方法Promise.

readFile('./notes.txt')
  .then(txt => console.log(txt))
  .catch(...);
Run Code Online (Sandbox Code Playgroud)


dav*_*vis 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);
});
Run Code Online (Sandbox Code Playgroud)


Apo*_*orv 6

您可以在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
Run Code Online (Sandbox Code Playgroud)


Jas*_*man 5

kriskowal的Q库包含应答回调函数。这样的方法:

obj.prototype.dosomething(params, cb) {
  ...blah blah...
  cb(error, results);
}
Run Code Online (Sandbox Code Playgroud)

可以用Q.ninvoke转换

Q.ninvoke(obj,"dosomething",params).
then(function(results) {
});
Run Code Online (Sandbox Code Playgroud)

  • 我发现这对谷歌来说很有用,因为它在Q潜在客户中混杂 (3认同)

use*_*503 5

当您有一些需要回调的函数并且希望它们返回承诺时,您可以使用此函数来进行转换。

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;
    }
}
Run Code Online (Sandbox Code Playgroud)


小智 5

在内置了 Promise 和 async 的 Node 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;
Run Code Online (Sandbox Code Playgroud)

如何使用:

let readdir = require('fs').readdir;
let promisify = require('./promisify');
let readdirP = promisify(readdir);

async function myAsyncFn(path) {
    let entries = await readdirP(path);
    return entries;
}
Run Code Online (Sandbox Code Playgroud)