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

IT技术 javascript node.js callback promise bluebird
2020-12-22 22:00:03

我想使用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”它?

6个回答

Promises 有状态,它们开始时处于待定状态,并且可以解决:

  • 完成意味着计算成功完成。
  • 拒绝意味着计算失败。

Promise 返回函数不应该 throw,它们应该返回拒绝。从Promise返回函数中抛出将迫使您同时使用 a} catch { a .catch使用promisified API 的人不期望Promise会抛出。如果您不确定异步 API 如何在 JS 中工作 - 请先查看此答案

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

因此,创建Promise通常意味着指定它们何时解决——这意味着它们何时进入已完成或拒绝阶段以指示数据可用(并且可以使用 访问.then)。

使用支持Promise构造函数的现代 Promise 实现,如原生 ES6 Promise:

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

然后,您将像这样使用生成的 promise:

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 中很常见。让我们看一下具有onSuccessand的常见情况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)表单,它的优点是允许我们编写非常接近该new Promise(fn)表单的表达式,如下所示:

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

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

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

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

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

一般来说,你不应该过多地手动Promise事情,大多数在设计时考虑到 Node 的Promise库以及 Node 8+ 中的原生Promise都有一个内置的方法来Promise节点背。例如

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

4. 带有节点样式回调的完整库:

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

Promise.promisifyAll(API);

或者Node 中使用本机Promise

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处理程序中时,您不需要Promise事情。.then处理程序返回Promise将使用该Promise的值来解决或拒绝。.then处理程序中抛出也是一种很好的做法,并且会拒绝Promise - 这就是著名的Promise抛出安全。
  • 在实际onload情况下,您应该使用addEventListener而不是onX.
嘿,为了 100% 公平,我不知道 jQuery 让你这样做$.Deferred(fn),如果你在接下来的 15 分钟内编辑它而不是现有示例,我相信我可以按时批准它:)
2021-02-09 22:00:03
@Roamer-1888 它被拒绝了,因为我没有及时看到并接受它。对于它的value,我认为添加虽然有用但并不太相关。
2021-02-10 22:00:03
这是一个很好的答案。您可能还想通过提及更新它util.promisify,Node.js 将从 RC 8.0.0 开始添加到其核心。它的工作方式与 Bluebird 没有太大区别Promise.promisify,但具有不需要额外依赖项的优势,以防您只需要本机 Promise。我已经写了一篇关于util.promisify的博客文章,供任何想要阅读更多关于该主题的人。
2021-02-11 22:00:03
Benjamin,我接受了你的编辑邀请,并在案例 2 中添加了一个 jQuery 示例。它在出现之前需要同行评审。希望你喜欢。
2021-02-13 22:00:03
本杰明,无论是否resolve()reject()被写入被重用,恕我冒昧,我的建议编辑是相关的,因为它提供了形式的jQuery的例子$.Deferred(fn),这是中缺乏。如果只包含一个 jQuery 示例,那么我建议它应该是这种形式而不是其他形式,var d = $.Deferred();因为应该鼓励人们使用经常被忽视的$.Deferred(fn)形式,另外,在这样的答案中,它使 jQuery 与使用Revealing Constructor Pattern 的库
2021-03-08 22:00:03

今天,我可以使用PromiseinNode.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 )

}

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 )

     })

}

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

Promise可以与async\awaitin一起使用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 )

     })

}

上述方法可以响应旧式回调和 Promise 用法的结果。

希望这可以帮助。

这些似乎没有显示如何转换为Promise。
2021-03-01 22:00:03

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

转换后

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

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

function promiseDOMready() {
    return new Promise(function(resolve) {
        if (document.readyState === "complete") return resolve();
        document.addEventListener("DOMContentLoaded", resolve);
    });
}
promiseDOMready().then(initOnLoad);
“已经完成”的分支不应该使用setTimeout(resolve, 0)(或者setImmediate,如果可用)来确保它被异步调用?
2021-02-17 22:00:03
@Alnitakresolve同步调用很好。Promise 的then处理程序由框架保证异步调用,无论是否resolve同步调用。
2021-02-24 22:00:03

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

请参阅改进对 Promise 的支持

已经有两个答案描述了这一点,为什么要发布第三个?
2021-02-16 22:00:03
@BenjaminGruenbaum 我赞成这一点,因为它不那么“混乱”且有效。最上面的那个有很多其他的东西,答案丢失了。
2021-02-27 22:00:03
仅仅因为该版本的节点现已发布,并且我已经报告了“官方”功能描述和链接。
2021-03-03 22:00:03