协慌网

登录 贡献 社区

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

我想使用 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 返回函数抛出将迫使您同时使用} catch {.catch 。使用承诺的 API 的人们不希望兑现承诺。如果您不确定 JS 中的异步 API 如何工作 - 请首先查看此答案。

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

因此,创建承诺通常意味着指定何时结算 - 即何时进入承诺阶段或拒绝阶段以指示数据可用(并且可以通过.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;
}

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

使用支持 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 deferred 的resolvereject方法是 “可分离的”。 IE。它们绑定到 jQuery.Deferred()的实例。并非所有的库都提供此功能。

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

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

通常,您不应该过多地手动分配内容,大多数基于 Node 设计的 Promise 库以及 Node 8 + 中的本机 Promise 都有内置的方法来使 NodeBback 富集。例如

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处理你不需要 promisify 事情。 .then处理程序返回一个诺言将以该诺言的值进行解析或拒绝。从.then处理程序中抛出也是很好的做法,并且会拒绝诺言 - 这就是著名的诺言抛出安全性。
  • 在实际的onload情况下,应该使用addEventListener而不是onX

今天,我可以将Node.js Promise用作普通的 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 ES7 async\await一起使用,以使程序流等待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

奖励:一种混合方法
(假设回调方法具有两个参数,分别为 error 和 result)

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 用法的结果。

希望这可以帮助。

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