Rewrite the title as: How to convert existing callback API to Promise form?
P粉268654873
2023-08-21 18:34:55
<p>I want to use promises to handle it, but the format of my callback API is as follows: </p>
<h3>1. DOM loading or other one-time events: </h3>
<pre class="brush:php;toolbar:false;">window.onload; // Set as callback function
...
window.onload = function() {
};</pre>
<h3>2. Ordinary callback function: </h3>
<pre class="brush:php;toolbar:false;">function request(onChangeHandler) {
...
}
request(function() {
// changed
...
});</pre>
<h3>3. Node-style callback function ("nodeback"): </h3>
<pre class="brush:php;toolbar:false;">function getStuff(dat, callback) {
...
}
getStuff("dataParam", function(err, data) {
...
})</pre>
<h3>4. The entire library uses Node-style callback functions: </h3>
<pre class="brush:php;toolbar:false;">API;
API.one(function(err, data) {
API.two(function(err, data2) {
API.three(function(err, data3) {
...
});
});
});</pre>
<h3>How do I use promises to handle this API and how do I "promisify" it? </h3>
Today, I can use
Promiseas a normal Javascript method inNode.js.A simple and basic
Promiseexample (using the KISS method):NormalJavascript asynchronous API code:
function divisionAPI (number, divider, successCallback, errorCallback) { if (divider == 0) { return errorCallback( new Error("Division by zero") ) } successCallback( number / divider ) }PromiseJavascript asynchronous 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 ) }) }(I recommend visiting this excellent source )
In addition,
Promisecan also be used withasync\awaitinES7to make the program flow wait for thefulfilledresult, as follows Shown:function getName () { return new Promise(function (fulfilled, rejected) { var name = "John Doe"; // 在调用fulfilled()方法之前等待3000毫秒 setTimeout ( function() { fulfilled( name ) }, 3000 ) }) } async function foo () { var name = await getName(); // 等待fulfilled结果! console.log(name); // 控制台在3000毫秒后输出"John Doe" } foo() // 调用foo()方法运行代码Using the same code, you can use the
.then()method:function getName () { return new Promise(function (fulfilled, rejected) { var name = "John Doe"; // 在调用fulfilled()方法之前等待3000毫秒 setTimeout ( function() { fulfilled( name ) }, 3000 ) }) } // 控制台在3000毫秒后输出"John Doe" getName().then(function(name){ console.log(name) })Promisecan also be used on any Node.js-based platform, such asreact-native.Bonus: A hybridapproach
(Assume that the callback method has two parameters, namely error and 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 ) }) }The above method can respond to the results of old-style callbacks and Promise usage at the same time.
Hope this helps.
Promises have a state, they start out in a pending state and can be resolved as:
Functions that return a promise should not throw an exception but should return a rejection. Throwing an exception from a function that returns a promise will force you to use both
} catch {and.catch. People using promise-based APIs don't want promises to throw exceptions. If you're not sure how the async API in JS works, first check out this answer.1. DOM loading or other one-time events:
Therefore, creating promises usually means specifying when they resolve - meaning when they move to the Fulfilled or Rejected stage to indicate that the data is available (and accessible using
.then).Use a modern promise implementation that supports
Promiseconstructors (such as native ES6 promises):function load() { return new Promise(function(resolve, reject) { window.onload = resolve; }); }You can then use the generated promise like this:
load().then(function() { // 在onload之后执行操作 });Use a library that supports delay (here we use $q as an example, but we will also use jQuery later):
function load() { var d = $q.defer(); window.onload = function() { d.resolve(); }; return d.promise; }Or use an API similar to jQuery to hook an event that occurs:
function done() { var d = $.Deferred(); $("#myObject").once("click",function() { d.resolve(); }); return d.promise(); }2. Ordinary callback:
These APIs are quite common because callbacks are common in JS. Let's look at
onSuccessandonFailin common cases:function getUserData(userId, onLoad, onFail) { …Use a modern promise implementation that supports
Promiseconstructors (such as native ES6 promises):function getUserDataAsync(userId) { return new Promise(function(resolve, reject) { getUserData(userId, resolve, reject); }); }Use a library that supports delay (here we're using jQuery as an example, but we also used $q before):
function getUserDataAsync(userId) { var d = $.Deferred(); getUserData(userId, function(res){ d.resolve(res); }, function(err){ d.reject(err); }); return d.promise(); }jQuery also provides the
$.Deferred(fn)form, which has the advantage of allowing us to write an expression very close to thenew Promise(fn)form, as shown below :function getUserDataAsync(userId) { return $.Deferred(function(dfrd) { getUserData(userId, dfrd.resolve, dfrd.reject); }).promise(); }Note: Here we take advantage of the fact that jQuery's deferred
resolveandrejectmethods are "detachable"; that is, they are bound to jQuery.Deferred()'s Example. Not all libraries provide this functionality.3. Node style callback ("nodeback"):
Node style callbacks (nodebacks) have a specific format where the callback is always the last parameter and its first parameter is the error. First manually convert it to a promise:
getStuff("dataParam", function(err, data) { …Convert to:
function getStuffAsync(param) { return new Promise(function(resolve, reject) { getStuff(param, function(err, data) { if (err !== null) reject(err); else resolve(data); }); }); }Using defer, you can do the following (we used Q as an example, although Q now supports a new syntax You should prefer that syntax ):
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; }In general you shouldn't manually convert things to promises too much, most promise libraries designed for Node as well as native promises in Node 8 have built-in methods for converting nodebacks to promises. For example
4. The entire library uses Node style callbacks:
There is no golden rule here, you can convert them into promises one by one. However, some promise implementations allow you to do this in batches, for example in Bluebird converting the nodeback API to a promise API is as simple as this:
Or use Native Promise in 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}), {});Notice:
.thenhandler. Returning a promise from the.thenhandler will be resolved or rejected using the value of the promise. It is also good practice to throw an exception from the.thenhandler, which will reject the promise - this is known as promise-throwing safety.onloadcase, you should useaddEventListenerinstead ofonX.