by Adham El Banhawy
由Adham El Banhawy
如何用JavaScript的回调函数做出承诺 (How to make a Promise out of a Callback function in JavaScript)
Back-end developers run into challenges all the time while building applications or testing code. As a developer who is fairly new and getting acquainted with those challenges, I have never run into a challenge or inconvenience more frequently — or more memorable — than with callback functions.
后端开发人员在构建应用程序或测试代码时始终遇到挑战。 作为一个相当新手并熟悉这些挑战的开发人员,我从来没有遇到比回调函数更频繁(或更令人难忘)的挑战或不便。
I am not going to delve too deeply into the details of callback and its pros and cons or alternatives like promises and async/await. For a more vivid explanation, you can check out this article which explains them thoroughly.
我不会深入探讨回调的细节及其优缺点或诸如promises和async / await之类的替代方法。 有关更生动的解释,您可以查看这篇文章 ,对它们进行彻底的解释。
回调地狱 (Callback Hell)
Callbacks are a useful feature of JavaScript’s that enables it make asynchronous calls. They are functions that are usually passed on as a second parameter to another function that is fetching data or doing an I/O operation that takes time to complete.
回调是JavaScript的一项有用功能,可使其进行异步调用。 它们是通常作为第二个参数传递给另一个函数的函数,该函数正在获取数据或执行需要花费时间才能完成的I / O操作。
For example, try making an API call using the request
module or connecting to a MongoDB database. But what if both calls depend on each other? What if the data you’re fetching is the MongoDB URL that you need to connect to?
例如,尝试使用request
进行API调用 模块或连接到MongoDB数据库。 但是,如果两个调用相互依赖怎么办? 如果您要获取的数据是您需要连接的MongoDB URL,该怎么办?
You’d have to nest these calls inside each other:
您必须将这些调用相互嵌套:
request.get(url, function(error, response, mongoUrl) {if(error) throw new Error("Error while fetching fetching data");MongoClient.connect(mongoUrl, function(error, client) {if(error) throw new Error("MongoDB connection error");console.log("Connected successfully to server"); const db = client.db("dbName");// Do some application logicclient.close();});});
Okay…so where’s the problem? Well, for one thing, the readability of the code suffers from this technique.
好吧...问题出在哪里? 好吧,一方面,这种技术使代码的可读性受到影响。
It may seem OK at first when the codebase is small. But this doesn’t scale well, especially if you go more layers deeper into the nested callbacks.
当代码库较小时,乍一看似乎还可以。 但这不能很好地扩展,特别是如果您在嵌套回调中更深入一些。
You will end up with a lot of closing brackets and curly braces that will confuse you and other developers no matter how neatly formatted your code is. There is a website called callbackhell that addresses this specific issue.
最终,您将得到大量的括弧和花括号,无论您的代码格式如何整洁,都会使您和其他开发人员感到困惑。 有一个名为callbackhell的网站可以解决此特定问题。
I hear some of you, including my naïve past self, telling me wrap it in an async
function then await
the callback function. This just doesn’t work.
我听到一些人,包括我过去的天真自我,告诉我将其包裹在async
函数然后await
回调函数。 这就是行不通的。
If there is any code block after the the function that uses callbacks, that code block will execute and will NOT wait for the callback.
如果在使用回调的函数之后有任何代码块,则该代码块将执行且不会 等待回调。
Here’s that mistake that I did before:
这是我之前犯过的错误:
var request = require('request');// WRONGasync function(){let joke;let url = "https://api.chucknorris.io/jokes/random"await request.get(url, function(error, response, data) {if(error) throw new Error("Error while fetching fetching data");let content = JSON.parse(data);joke = content.value;});console.log(joke); // undefined};// Wrongasync function(){let joke;let url = "https://api.chucknorris.io/jokes/random"request.get(url, await function(error, response, data) {if(error) throw new Error("Error while fetching fetching data");let content = JSON.parse(data);joke = content.value;});console.log(joke); // undefined};
Some more experienced devs might say “Just use a different library that uses promises to do the same thing, like axios, or just use fetch”. Sure I can in that scenario, but that’s just running away from the problem.
一些经验更丰富的开发人员可能会说:“只需使用一个不同的库即可使用诺言来完成相同的工作,例如axios , 或仅使用提取 ” 。 当然可以,但是这确实可以解决问题。
Besides, this is just an example. Sometimes you can be locked into using a library that doesn’t support promises with no alternatives. Like using software development kits (SDKs) to communicate with platforms like Amazon Web Services (AWS), Twitter, or Facebook.
此外,这仅是示例。 有时您可能会被锁定为使用不支持promise且没有其他选择的库。 就像使用软件开发套件(SDK)与Amazon Web Services(AWS),Twitter或Facebook之类的平台进行通信一样。
Sometimes, even using a callback to do a very simple call with a quick I/O or CRUD operation is fine, and no other logic depends on its results. But you might be constrained by the runtime environment like in a Lambda function which would kill all process once the main thread finishes, regardless of any asynchronous calls that did not complete.
有时,即使使用回调通过快速的I / O或CRUD操作进行非常简单的调用也可以,并且其他逻辑均不取决于其结果。 但是您可能会受到Lambda函数之类的运行时环境的约束,该函数会在主线程完成后终止所有进程,而不管所有未完成的异步调用如何。
解决方案1(简单):使用Node的“ util”模块 (Solution 1 (easy): Use Node’s “util” module)
The solution is surprisingly simple. Even if you’re a little uncomfortable with the idea of promises in JavaScript, you will love how you can solve this issue using them.
解决方案非常简单。 即使您对JavaScript中的Promise想法有点不满意,您也会喜欢如何使用Promise解决此问题。
As pointed out by Erop and Robin in the comments, Nodejs version 8 and above now support turning callback functions into promises using the built-in util module.
正如Erop和Robin在评论中所指出的那样,Nodejs 8及更高版本现在支持使用内置util模块将回调函数转化为promises。
const request = require('request');const util = require('util');const url = "https://api.chucknorris.io/jokes/random";// Use the util to promisify the request methodconst getChuckNorrisFact = util.promisify(request);// Use the new method to call the API in a modern then/catch patterngetChuckNorrisFact(url).then(data => {let content = JSON.parse(data.body);console.log('Joke: ', content.value);}).catch(err => console.log('error: ', err))
The above code solves the problem neatly using the util.promisify method available from nodejs core library.
上面的代码使用util.promisify巧妙地解决了问题 可从nodejs核心库获得的方法。
All you have to do is use the callback function as an argument to util.promisify, and store it an a variable. In my case, that’s getChuckNorrisFact.Then you use that variable as a function that you can use like a promise with the .then() and the .catch() methods.
您要做的就是将回调函数用作util.promisify的参数,并将其存储为变量。 就我而言,这是getChuckNorrisFact。然后使用该变量作为一个功能,您可以使用像的。然后承诺()和.catch()方法。
解决方案2(涉及):将回调变成承诺 (Solution 2 (involved): Turn the Callback into a Promise)
Sometimes, using the request and util libraries is just not possible, whether it’s because of a legacy environment/code base or doing the requests from the client-side browser, you have to wrap a promise around your callback function.
有时,无论是由于旧版环境/代码库还是从客户端浏览器执行请求,都无法使用request和util库,您必须在回调函数周围包裹一个Promise。
Let’s take the Chuck Norris example above, and turn that into a promise.
让我们以上面的Chuck Norris为例,并将其变成一个承诺。
var request = require('request');
let url = "https://api.chucknorris.io/jokes/random";// A function that returns a promise to resolve into the data //fetched from the API or an error
let getChuckNorrisFact = (url) => {return new Promise((resolve, reject) => {request.get(url, function(error, response, data){if (error) reject(error);let content = JSON.parse(data);let fact = content.value;resolve(fact);})});
};getChuckNorrisFact(url).then(fact => console.log(fact) // actually outputs a string
).catch(error => console.(error)
);
In the code above, I put the callback-based request
function inside a Promise wrapper Promise( (resolve, reject) => { //callback function})
. This wrapper allows us to call the getChuckNorrisFact
function like a promise with the .then()
and .catch()
methods. When the getChuckNorrisFact
is called, it executes the request to the API and waits for either a resolve()
or a reject()
statement to execute. In the callback function, you simply pass the retrieved data into the resolve or reject methods.
在上面的代码中,我将基于回调的request
函数放入Promise包装器Promise( (resolve, reject) => { //callback function})
。 这个包装器允许我们像getChuckNorrisFact
.then()
和.catch()
方法一样调用getChuckNorrisFact
函数。 调用getChuckNorrisFact
,它将执行对API的请求,并等待 resolve()
或reject()
语句执行。 在回调函数中,您只需将检索到的数据传递到resolve或reject方法。
Once the data (in this case, an awesome Chuck Norris fact) is fetched and passed to the resolver, the getChuckNorrisFact
executes the then()
method. This will return the result that you can use inside a function inside the then()
to do your desired logic — in this case displaying it to the console.
一旦获取了数据(在这种情况下,这是一个很棒的Chuck Norris事实)并将其传递给解析器,则getChuckNorrisFact
执行then()
方法。 这将返回结果,您可以在then()
内部的函数中使用该结果来执行所需的逻辑—在这种情况下,将其显示在控制台上。
You can read more about it in the MDN Web Docs.
您可以在MDN网络文档中阅读有关此内容的更多信息。
翻译自: https://www.freecodecamp.org/news/how-to-make-a-promise-out-of-a-callback-function-in-javascript-d8ec35d1f981/