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

我想使用 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) {
            ...
        });
    });
});

如何在承诺中使用API,如何“抵押”它?


答案 1

承诺有状态,它们开始时是待定的,可以结算到:

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

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

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

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

使用支持构造函数的现代承诺实现,如本机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;
}

或者使用像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) { …

使用支持构造函数的现代承诺实现,如本机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延迟和方法是“可分离的”这一事实;即。它们绑定到 jQuery.Deferred() 的实例。并非所有库都提供此功能。resolvereject

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

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

一般来说,你不应该过多地手动处理事情,大多数在设计时都考虑到了Node的承诺库以及Node 8+中的原生承诺都有一个内置的方法来消除nodebacks。例如

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

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

这里没有黄金法则,你一个接一个地提出它们。但是,一些承诺实现允许您批量执行此操作,例如在Bluebird中,将nodeback API转换为承诺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.then.then
  • 在实际情况下,应使用 而不是 。onloadaddEventListeneronX

答案 2

今天,我可以将 in 用作普通的 Javascript 方法。PromiseNode.js

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

平原Javascript Async API code:

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

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

    successCallback( number / divider )

}

承诺Javascript Async API code:

function divisionAPI (number, divider) {

    return new Promise(function (fulfilled, rejected) {

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

        fulfilled( number / divider )

     })

}

(我建议参观这个美丽的来源)

也可以与 in 一起使用,以使程序流等待如下结果:Promiseasync\awaitES7fullfiled

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 )

     })

}

上述方法可以响应老式回调和承诺用法的结果。

希望这有帮助。