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

我如何在promise中使用API,如何“promise”它?


当前回答

承诺具有状态,它们开始时是待定的,可以解决:

这意味着计算成功完成。拒绝表示计算失败。

承诺返回函数不应该抛出,而是应该返回拒绝。从promise返回函数抛出将迫使您同时使用一个}catch{和一个.catch。使用promise API的人不希望抛出promise。如果您不确定异步API在JS中的工作方式,请先看这个答案。

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

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

使用支持promise构造函数的现代promise实现,如本机ES6 promise:

function load() {
    return new Promise(function(resolve, reject) {
        window.onload = resolve;
    });
}

然后,您可以这样使用由此产生的承诺:

load().then(function() {
    // Do things after onload
});

对于支持deferred的库(让我们在这里使用$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构造函数的现代promise实现,如本机ES6 promise:

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)表单,它的优点是允许我们编写一个非常类似于新Promise(fn)格式的表达式,如下所示:

function getUserDataAsync(userId) {
    return $.Deferred(function(dfrd) {
        getUserData(userId, dfrd.resolve, dfrd.reject);
    }).promise();
}

注意:这里我们利用了jQuerydeferred的解析和拒绝方法是“可分离的”这一事实;即,它们绑定到jQuery.Deferred()的实例。并非所有的库都提供此功能。

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

节点样式回调(nodebacks)具有特定的格式,其中回调总是最后一个参数,其第一个参数是错误。让我们先手动承诺:

getStuff("dataParam", function(err, data) { …

To:

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

使用deferres,您可以执行以下操作(让我们在本例中使用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;   
}

一般来说,您不应该手动过多地promise,大多数考虑到Node的promise库以及Node 8+中的原生promise都有一个内置的promise nodeback方法。例如

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(([key, v]) => ({key, fn: promisify(v)}))
                         .reduce((o, p) => Object.assign(o, {[p.key]: p.fn}), {});

笔记:

当然,当你在.then处理程序中时,你不需要承诺。从.then处理程序返回promise将使用该promise的值进行解析或拒绝。从.then处理程序投掷也是一种很好的做法,它会拒绝承诺-这就是著名的承诺投掷安全。在实际的onload情况下,应该使用addEventListener而不是onX。

其他回答

回调样式函数总是这样(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)和append(如javascript function.prototype.push)。因此,我们现在可以将回调样式函数转换为promise样式函数:

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

toPromise和checkErr函数由berserk库拥有,它是ramda.js(由我创建)的函数式编程库fork。

希望这个答案对你有用。

下面是如何将函数(回调API)转换为promise的实现。

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

我的promisify版本的回调函数是P函数:

var P=函数(){var self=this;var方法=参数[0];var params=数组.原型.切片.调用(参数,1);return new Promise((resolve,reject)=>{if(method&&typeof(method)==“函数”){params.push(函数(错误,状态){if(!err)返回解析(状态)否则返回拒绝(err);});方法.应用(self,params);}否则返回拒绝(新错误(“不是函数”));});}var callback=函数(par,回调){var rnd=数学地板(Math.random()*2)+1;返回rnd>1?callback(null,par):回调(new Error(“trap”));}callback(“callback”,(err,state)=>错误?console.error(错误):console.log(状态))callback(“callback”,(err,state)=>错误?console.error(错误):console.log(状态))callback(“callback”,(err,state)=>错误?console.error(错误):console.log(状态))callback(“callback”,(err,state)=>错误?console.error(错误):console.log(状态))P(回调,“promise”).then(v=>console.log(v)).catch(e=>console.error(e))P(回调,“promise”).then(v=>console.log(v)).catch(e=>console.error(e))P(回调,“promise”).then(v=>console.log(v)).catch(e=>console.error(e))P(回调,“promise”).then(v=>console.log(v)).catch(e=>console.error(e))

P函数要求回调签名必须是回调(error,result)。

如果有几个函数需要回调,并且希望它们返回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;
    }
}

您可以在NodeJS中使用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