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

Javascript 如何将现有回调API转换为承诺?,javascript,node.js,callback,promise,bluebird,Javascript,Node.js,Callback,Promise,Bluebird,我想使用Promissions,但我有一个回调API,其格式如下: 1.DOM加载或其他一次性事件: 2.普通回调: 3.节点样式回调(“节点回退”): 4.具有节点样式回调的整个库: 我如何在Promissions中使用API,我如何“承诺”它?Promissions有状态,它们开始时是挂起的,可以解决为: 完成表示计算成功完成 被拒绝表示计算失败 承诺返回函数,它们应该返回拒绝。从承诺返回函数中抛出将迫使您同时使用}catch{和.catch。使用promisified API的人不希望

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

1.DOM加载或其他一次性事件: 2.普通回调: 3.节点样式回调(“节点回退”): 4.具有节点样式回调的整个库:
我如何在Promissions中使用API,我如何“承诺”它?Promissions有状态,它们开始时是挂起的,可以解决为:

  • 完成表示计算成功完成
  • 被拒绝表示计算失败
承诺返回函数,它们应该返回拒绝。从承诺返回函数中抛出将迫使您同时使用
}catch{
.catch
。使用promisified API的人不希望抛出承诺。如果您不确定异步API在JS中如何工作,请先

1.DOM加载或其他一次性事件: 因此,创建承诺通常意味着指定它们何时结算,也就是说当它们移动到完成或拒绝阶段时,表明数据可用(并且可以通过
访问。然后

使用支持
promise
构造函数(如本机ES6承诺)的现代承诺实现:

function load() {
    return new Promise(function(resolve, reject) {
        window.onload = resolve;
    });
}
function getUserDataAsync(userId) {
    return new Promise(function(resolve, reject) {
        getUserData(userId, resolve, reject);
    });
}
然后,您将使用这样的结果承诺:

load().then(function() {
    // Do things after onload
});
对于支持deferred的库(在这里我们使用$q作为示例,但稍后我们也将使用jQuery):

或者使用类似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 load() {
    return new Promise(function(resolve, reject) {
        window.onload = resolve;
    });
}
function getUserDataAsync(userId) {
    return new Promise(function(resolve, reject) {
        getUserData(userId, resolve, reject);
    });
}
对于支持deferred的库(让我们在这里使用jQuery作为示例,但我们也使用了上面的$q):

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

function getUserDataAsync(userId) {
    return $.Deferred(function(dfrd) {
        getUserData(userId, dfrd.resolve, dfrd.reject);
    }).promise();
}
注意:这里我们利用了一个事实,即jQuery deferred的
resolve
reject
方法是“可分离的”;即,它们绑定到jQuery.deferred()的实例。并非所有的lib都提供此功能

3.节点样式回调(“节点回退”): 节点样式回调(Nodeback)具有特定的格式,其中回调始终是最后一个参数,其第一个参数是错误。让我们首先手动提示一个:

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

对于延迟,您可以执行以下操作(在本例中使用Q,尽管Q现在支持新语法):

一般来说,您不应该过多地手动实现承诺,大多数在设计时考虑到节点以及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);
或者在节点中使用本机承诺:

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}), {});
注:

  • 当然,当你在
    处理程序中时,你不需要承诺任何事情。从
    返回承诺。然后
    处理程序将根据承诺的价值来解决或拒绝。从
    处理程序抛出也是一个好的实践,并且会拒绝承诺-这就是著名的承诺抛出安全
  • 在实际的
    onload
    情况下,应该使用
    addEventListener
    而不是
    onX

我不认为@Benjamin的
窗口。onload
建议会一直有效,因为它无法检测加载后是否调用它。我已经被咬了很多次。下面是一个应该始终有效的版本:

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

kriskowal的Q库包含了对promise函数的回调。 这样的方法:

obj.prototype.dosomething(params, cb) {
  ...blah blah...
  cb(error, results);
}
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
        )
    )
)
可以用Q.ninvoke转换

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

您可以将JavaScript本机承诺与节点JS一起使用

我的云9代码链接:


如果有几个函数接受回调,并且希望它们返回承诺,则可以使用此函数进行转换

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

使用普通的老式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);
});

今天,我可以在
Node.js
中使用
Promise
作为一种简单的Javascript方法

承诺的一个简单而基本的例子(使用方式):

PlainJavascript异步API代码:

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

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

    successCallback( number / divider )

}
function divisionAPI (number, divider) {

    return new Promise(function (fulfilled, rejected) {

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

        fulfilled( number / divider )

     })

}
Promise
Javascript异步API代码:

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

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

    successCallback( number / divider )

}
function divisionAPI (number, divider) {

    return new Promise(function (fulfilled, rejected) {

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

        fulfilled( number / divider )

     })

}
(我建议参观)

另外,
Promise
还可以与
ES7
中的
async\wait
一起使用,使程序流等待
fullfield
结果,如下所示:

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

奖金:一种混合
方法
(假定回调方法有两个参数,即error和result)

上述方法可以响应旧式回调的结果并承诺使用


希望这有帮助。

您可以在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()

在内置承诺和异步的节点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;
}

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

这与其他answ中建议的方法没有太大区别
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) })
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 )

     })

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

}
// 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;
}
const fs = require('fs');
const util = require('util');

const readFile = util.promisify(fs.readFile);
readFile('./notes.txt')
  .then(txt => console.log(txt))
  .catch(...);
const fs = require('fs');
const util = require('util');

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

readFile('/some/file')
  .then((data) => { /** ... **/ })
  .catch((err) => { /** ... **/ });
//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
        )
    )
)
const {readdir} = require('fs')
const readdirP = toPromise(readdir)
readdir(Path)
    .then(
        (files) => console.log(files),
        (err) => console.log(err)
    )
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)
});
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);
    // ...
  });
doAsync(myFunc).apply(context, params)
  .then(result => { /*...*/ });
const promisify = require('es6-promisify');

const promisedFn = promisify(callbackedFn, args);
// @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)
}
const promesify = fn => {
  return (...params) => ({
    then: cbThen => ({
      catch: cbCatch => {
        fn(...params, cbThen, cbCatch);
      }
    })
  });
};
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');

// given you've defined this `Future` fn somewhere:
const Future = fn => {return new Promise((r,t) => fn(r,t))}

// define an eventFn that takes a promise `resolver`
const eventFn = resolve => {
  // do event related closure actions here. When finally done, call `resolve()`
  something.oneventfired = e => {resolve(e)}
}

// invoke eventFn in an `async` workflowFn using `Future`
// to obtain a `promise` wrapper
const workflowFn = async () => {await Future(eventFn)}
class PromiseEx extends Promise {
  resolve(v,...a) {
    this.settled = true; this.settledValue = v;
    return(this.resolve_(v,...a))
  }
  reject(v,...a) {
    this.settled = false; this.settledValue = v;
    return(this.reject_(v,...a))
  }
  static Future(fn,...args) {
    let r,t,ft = new PromiseEx((r_,t_) => {r=r_;t=t_})
    ft.resolve_ = r; ft.reject_ = t; fn(ft,...args);
    return(ft)
  }
}