JavaScript is synchronous. This means that it will execute your code block by order after hoisting. Before the code executes, var
and function
declarations are “hoisted” to the top of their scope.
JavaScript是同步的。 這意味著它將在提升后按順序執行代碼塊。 在代碼執行之前,將var
和function
聲明“提升”到其作用域的頂部。
This is an example of a synchronous code:
這是一個同步代碼的示例:
console.log('1')console.log('2')console.log('3')
This code will reliably log “1 2 3".
此代碼將可靠地記錄為“ 1 2 3”。
Asynchronous requests will wait for a timer to finish or a request to respond while the rest of the code continues to execute. Then when the time is right a callback will spring these asynchronous requests into action.
異步請求將等待計時器完成或請求響應,而其余代碼將繼續執行。 然后,當時間合適時, 回調將使這些異步請求生效。
This is an example of an asynchronous code:
這是一個異步代碼的示例:
console.log('1')setTimeout(function afterTwoSeconds() {console.log('2')
}, 2000)console.log('3')
This will actually log “1 3 2”, since the “2” is on a setTimeout
which will only execute, by this example, after two seconds. Your application does not hang waiting for the two seconds to finish. Instead it keeps executing the rest of the code and when the timeout is finished it returns to afterTwoSeconds.
實際上,這將記錄為“ 1 3 2”,因為“ 2”位于setTimeout
上,在此示例中,僅在兩秒鐘后執行。 您的應用程序不會掛起等待兩秒鐘。 相反,它將繼續執行其余代碼,并且當超時完成時,它將返回至afterTwoSeconds。
You may ask “Why is this useful?” or “How do I get my async code to become sync?”. Hopefully I can show you the answers.
您可能會問“為什么這有用?” 或“如何使我的異步代碼變得同步?”。 希望我能給你答案。
“問題” (“The problem”)
Let us say our goal is to search for a GitHub user and get all the repositories of that user. The thing is we don’t know the exact name of the user. So we have to list all the users with similar name and their respective repositories.
讓我們說我們的目標是搜索GitHub用戶并獲取該用戶的所有存儲庫。 問題是我們不知道用戶的確切名稱。 因此,我們必須列出名稱相似的所有用戶及其各自的存儲庫。
Doesn’t need to super fancy, something like this
不需要花哨的東西,像這樣
In these examples the request code will use XHR (XMLHttpRequest). You can replace it with jQuery $.ajax
or the more recent native approach called fetch
. Both will give you the promises approach out of the gate.
在這些示例中,請求代碼將使用XHR( XMLHttpRequest )。 您可以將其替換為jQuery $.ajax
或較新的本地方法fetch
。 兩者都會使您脫穎而出。
It will be slightly changed depending on your approach but as a starter:
首先,它會根據您的方法稍有變化:
// url argument can be something like 'https://api.github.com/users/daspinola/repos'function request(url) {const xhr = new XMLHttpRequest();xhr.timeout = 2000;xhr.onreadystatechange = function(e) {if (xhr.readyState === 4) {if (xhr.status === 200) {// Code here for the server answer when successful} else {// Code here for the server answer when not successful}}}xhr.ontimeout = function () {// Well, it took to long do some code here to handle that}xhr.open('get', url, true)xhr.send();
}
Remember that in these examples the important part is not what the end result of the code is. Instead your goal should be to understand the differences of the approaches and how you can leverage them for your development.
請記住,在這些示例中,重要的部分不是代碼的最終結果。 相反,您的目標應該是了解方法的差異以及如何利用它們進行開發。
打回來 (Callback)
You can save a reference of a function in a variable when using JavaScript. Then you can use them as arguments of another function to execute later. This is our “callback”.
使用JavaScript時,可以將函數的引用保存在變量中。 然后,您可以將它們用作另一個函數的參數,以便以后執行。 這是我們的“回調”。
One example would be:
一個例子是:
// Execute the function "doThis" with another function as parameter, in this case "andThenThis". doThis will execute whatever code it has and when it finishes it should have "andThenThis" being executed.doThis(andThenThis)// Inside of "doThis" it's referenced as "callback" which is just a variable that is holding the reference to this functionfunction andThenThis() {console.log('and then this')
}// You can name it whatever you want, "callback" is common approachfunction doThis(callback) {console.log('this first')// the '()' is when you are telling your code to execute the function reference else it will just log the referencecallback()
}
Using the callback
to solve our problem allows us to do something like this to the request
function we defined earlier:
使用callback
來解決我們的問題,使我們可以對之前定義的request
函數執行以下操作:
function request(url, callback) {const xhr = new XMLHttpRequest();xhr.timeout = 2000;xhr.onreadystatechange = function(e) {if (xhr.readyState === 4) {if (xhr.status === 200) {callback(null, xhr.response)} else {callback(xhr.status, null)}}}xhr.ontimeout = function () {console.log('Timeout')}xhr.open('get', url, true)xhr.send();
}
Our function for the request will now accept a callback
so that when a request
is made it will be called in case of error and in case of success.
現在,我們用于請求的函數將接受callback
以便在發出request
時將在錯誤和成功的情況下調用它。
const userGet = `https://api.github.com/search/users?page=1&q=daspinola&type=Users`request(userGet, function handleUsersList(error, users) {if (error) throw errorconst list = JSON.parse(users).itemslist.forEach(function(user) {request(user.repos_url, function handleReposList(err, repos) {if (err) throw err// Handle the repositories list here})})
})
Breaking this down:
分解如下:
- We make a request to get a user’s repositories 我們請求獲取用戶的存儲庫
After the request is complete we use callback
handleUsersList
請求完成后,我們使用回調
handleUsersList
If there is no error then we parse our server response into an object using
JSON.parse
如果沒有錯誤,則使用
JSON.parse
服務器響應解析為一個對象Then we iterate our user list since it can have more than one
然后,我們迭代用戶列表,因為它可以有多個
For each user we request their repositories list.
對于每個用戶,我們都請求他們的存儲庫列表。
We will use the url that returned per user in our first response
我們將在第一個響應中使用每個用戶返回的網址
We call
我們稱之為
repos_url
as the url for our next requests or from the first responserepos_url
作為下一個請求或第一個響應的URLWhen the request has completed the callback, we will call
當請求完成回調后,我們將調用
This will handle either its error or the response with the list of repositories for that user
這將使用該用戶的存儲庫列表來處理其錯誤或響應
Note: Sending the error first as parameter is a common practice especially when using Node.js.
注意 :通常首先發送錯誤作為參數,尤其是在使用Node.js時。
A more “complete” and readable approach would be to have some error handling. We would keep the callback separate from the request execution.
一種更“完整”且易讀的方法是要進行一些錯誤處理。 我們將使回調與請求執行分開。
Something like this:
像這樣:
try {request(userGet, handleUsersList)
} catch (e) {console.error('Request boom! ', e)
}function handleUsersList(error, users) {if (error) throw errorconst list = JSON.parse(users).itemslist.forEach(function(user) {request(user.repos_url, handleReposList)})
}function handleReposList(err, repos) {if (err) throw err// Handle the repositories list hereconsole.log('My very few repos', repos)
}
This ends up having problems like racing and error handling issues. Racing happens when you don’t control which user you will get first. We are requesting the information for all of them in case there is more than one. We are not taking an order into account. For example, user 10 can come first and user 2 last. We have a possible solution later in the article.
最終會出現競速和錯誤處理問題。 當您無法控制將首先獲得哪個用戶時,就會發生競速。 如果不止一個,我們要求提供所有這些信息。 我們沒有考慮訂單。 例如,用戶10可以排在第一位,而用戶2可以排在最后。 我們將在本文后面提供一個可能的解決方案。
The main problem with callbacks is that maintenance and readability can become a pain. It sort of already is and the code does hardly anything. This is known as callback hell which can be avoided with our next approach.
回調的主要問題是維護和可讀性會變得很痛苦。 它已經存在,并且代碼幾乎沒有任何作用。 這被稱為回調地獄 ,可以通過我們的下一種方法來避免。
承諾 (Promises)
Promises you can make your code more readable. A new developer can come to the code base and see a clear order of execution to your code.
保證您可以使代碼更具可讀性。 新的開發人員可以進入代碼庫,并查看代碼的清晰執行順序。
To create a promise you can use:
要創建承諾,您可以使用:
const myPromise = new Promise(function(resolve, reject) {// code hereif (codeIsFine) {resolve('fine')} else {reject('error')}})myPromise.then(function whenOk(response) {console.log(response)return response}).catch(function notOk(err) {console.error(err)})
Let us decompose it:
讓我們分解一下:
A promise is initialized with a
function
that hasresolve
andreject
statements使用具有
resolve
和reject
語句的function
初始化promiseMake your async code inside the
Promise
function使您的異步代碼在
Promise
函數中Make your async code inside the
Promise
functionresolve
when everything happens as desired當一切都按需進行時,使
Promise
函數中的異步代碼Promise
resolve
Otherwise
除此以外
reject
reject
When a
resolve
is found the.then
method will execute for thatPromise
找到
resolve
,.then
方法將針對該Promise
執行When a
當一個
reject
is found the.catch
will be triggered發現
reject
,將觸發.catch
Things to bear in mind:
注意事項:
resolve
andreject
only accept one parameterresolve
和reject
僅接受一個參數resolve
andreject
only accept one parameterresolve(‘yey’, ‘works’)
will only send ‘yey’ to the.then
callback functionresolve
和reject
只接受一個參數resolve('yey', 'works')
將只發送“yey”到.then
回調函數If you chain multiple
.then
如果鏈接多個
.then
Add a
添加一個
return
if you want the next.then
value not to beundefined
return
如果你想下一個.then
值不被undefined
When a
reject
is caught with.catch
if you have a.then
chained to it當一個
reject
被抓到.catch
如果你有一個.then
鏈接到它It will still execute that
它仍然會執行
.then
.then
You can see the
你可以看到
.then
as an “always executes” and you can check an example in this comment.then
作為“始終執行”,您可以在此注釋中查看示例With a chain on
.then
if an error happens on the first one如果鏈上
.then
如果第一個發生錯誤It will skip subsequent
隨后將跳過
.then
until it finds a.catch
.then
,直到找到一個.catch
A promise has three states
一個承諾有三個狀態
A promise has three statespending
一個承諾有三個待處理狀態
When waiting for a
resolve
orreject
to happen等待
resolve
或reject
發生時When waiting for a
resolve
orreject
to happenresolved當等待
resolve
或reject
發生解決時When waiting for a
resolve
orreject
to happenresolved rejected在等待
resolve
或reject
發生時, 解決 被拒絕Once it’s in a
resolved
orrejected
state一旦處于已
resolved
或已rejected
狀態It cannot be changed
不能改變
Note: You can create promises without the function at the moment of declarations. The way that I’m showing it is only a common way of doing it.
注意 :在聲明時,您可以創建沒有功能的promise。 我展示它的方式只是這樣做的一種常見方式。
“Theory, theory, theory…I’m confused” you may say.
您可能會說:“理論,理論,理論……我很困惑”。
Let’s use our request example with a promise to try to clear things up:
讓我們將請求示例與一個承諾一起使用來嘗試清除問題:
function request(url) {return new Promise(function (resolve, reject) {const xhr = new XMLHttpRequest();xhr.timeout = 2000;xhr.onreadystatechange = function(e) {if (xhr.readyState === 4) {if (xhr.status === 200) {resolve(xhr.response)} else {reject(xhr.status)}}}xhr.ontimeout = function () {reject('timeout')}xhr.open('get', url, true)xhr.send();})
}
In this scenario when you execute request
it will return something like this:
在這種情況下,當您執行request
,它將返回如下內容:
const userGet = `https://api.github.com/search/users?page=1&q=daspinola&type=Users`const myPromise = request(userGet)console.log('will be pending when logged', myPromise)myPromise.then(function handleUsersList(users) {console.log('when resolve is found it comes here with the response, in this case users ', users)const list = JSON.parse(users).itemsreturn Promise.all(list.map(function(user) {return request(user.repos_url)}))}).then(function handleReposList(repos) {console.log('All users repos in an array', repos)}).catch(function handleErrors(error) {console.log('when a reject is executed it will come here ignoring the then statement ', error)})
This is how we solve racing and some of the error handling problems. The code is still a bit convoluted. But its a way to show you that this approach can also create readability problems.
這就是我們解決賽車以及一些錯誤處理問題的方式。 代碼仍然有些復雜。 但是,這是一種向您展示這種方法還會造成可讀性問題的方法。
A quick fix would be to separate the callbacks like so:
一個快速的解決方法是像這樣將回調分開:
const userGet = `https://api.github.com/search/users?page=1&q=daspinola&type=Users`const userRequest = request(userGet)// Just by reading this part out loud you have a good idea of what the code does
userRequest.then(handleUsersList).then(repoRequest).then(handleReposList).catch(handleErrors)function handleUsersList(users) {return JSON.parse(users).items
}function repoRequest(users) {return Promise.all(users.map(function(user) {return request(user.repos_url)}))
}function handleReposList(repos) {console.log('All users repos in an array', repos)
}function handleErrors(error) {console.error('Something went wrong ', error)
}
By looking at what userRequest
is waiting in order with the .then
you can get a sense of what we expect of this code block. Everything is more or less separated by responsibility.
通過查看與userRequest
等待的userRequest
.then
您可以了解我們對這個代碼塊的期望。 一切或多或少都由責任分隔。
This is “scratching the surface” of what Promises are. To have a great insight on how they work I cannot recommend enough this article.
這就是“承諾”的“表面”。 要對它們的工作原理有一個深刻的了解,我不能推薦這篇文章 。
發電機 (Generators)
Another approach is to use the generators. This is a bit more advance so if you are starting out feel free to jump to the next topic.
另一種方法是使用發電機。 這還有一些進步,因此,如果您剛開始,請隨時跳到下一個主題。
One use for generators is that they allow you to have async code looking like sync.
生成器的一種用途是,它們使您可以擁有看起來像同步的異步代碼。
They are represented by a *
in a function and look something like:
它們在函數中由*
表示,外觀類似于:
function* foo() {yield 1const args = yield 2console.log(args)
}
var fooIterator = foo()console.log(fooIterator.next().value) // will log 1
console.log(fooIterator.next().value) // will log 2fooIterator.next('aParam') // will log the console.log inside the generator 'aParam'
Instead of returning with a return
, generators have a yield
statement. It stops the function execution until a .next
is made for that function iteration. It is similar to .then
promise that only executes when resolved comes back.
生成器具有yield
語句,而不是返回return
。 它會停止函數執行,直到為該函數迭代創建.next
為止。 它類似于.then
承諾,僅在解決方案返回時才執行。
Our request function would look like this:
我們的請求函數如下所示:
function request(url) {return function(callback) {const xhr = new XMLHttpRequest();xhr.onreadystatechange = function(e) {if (xhr.readyState === 4) {if (xhr.status === 200) {callback(null, xhr.response)} else {callback(xhr.status, null)}}}xhr.ontimeout = function () {console.log('timeout')}xhr.open('get', url, true)xhr.send()}
}
We want to have the url
as an argument. But instead of executing the request out of the gate we want it only when we have a callback to handle the response.
我們希望將url
作為參數。 但是,僅當我們具有處理響應的回調時,我們才需要它,而不是從門外執行請求。
Our generator
would be something like:
我們的generator
將是這樣的:
function* list() {const userGet = `https://api.github.com/search/users?page=1&q=daspinola&type=Users`const users = yield request(userGet)yieldfor (let i = 0; i<=users.length; i++) {yield request(users[i].repos_url)}
}
It will:
它會:
Wait until the first
request
is prepared等到第一個
request
準備好Return a
function
reference expecting acallback
for the firstrequest
返回一個
function
引用,期望第一個request
有callback
Our
我們的
request
function accepts aurl
request
函數接受url
and returns a
并返回一個
function
that expects acallback
需要
callback
function
Expect a
users
to be sent in the next.next
期望在下一個
.next
發送users
Iterate over
users
遍歷
users
Wait for a
.next
for each of theusers
等待每個
users
的.next
- Return their respective callback function 返回各自的回調函數
So an execution of this would be:
因此,將執行以下操作:
try {const iterator = list()iterator.next().value(function handleUsersList(err, users) {if (err) throw errconst list = JSON.parse(users).items// send the list of users for the iteratoriterator.next(list)list.forEach(function(user) {iterator.next().value(function userRepos(error, repos) {if (error) throw repos// Handle each individual user repo hereconsole.log(user, JSON.parse(repos))})})})
} catch (e) {console.error(e)
}
We could separate the callback functions like we did previously. You get the deal by now, a takeaway is that we now can handle each individual user repository list individually.
我們可以像以前一樣分離回調函數。 您現在就達成了交易,一個收獲是,我們現在可以分別處理每個單獨的用戶存儲庫列表。
I have mixed felling about generators. On one hand I can get a grasp of what is expected of the code by looking at the generator.
我對發電機一無所知。 一方面,通過查看生成器,我可以了解代碼的期望。
But its execution ends up having similar problems to the callback hell.
但是其執行最終會遇到與回調地獄類似的問題。
Like async/await, a compiler is recommended. This is because it isn’t supported in older browser versions.
與async / await一樣 ,建議使用編譯器。 這是因為較舊的瀏覽器版本不支持該功能。
Also it isn’t that common in my experience. So it may generate confusing in codebases maintained by various developers.
根據我的經驗,這也不是那么普遍。 因此,它可能會在由各種開發人員維護的代碼庫中引起混亂。
An awesome insight of how generators work can be found in this article. And here is another great resource.
在本文中可以找到有關生成器工作原理的真知灼見。 這是另一個很棒的資源 。
異步/等待 (Async/Await)
This method seems like a mix of generators with promises. You just have to tell your code what functions are to be async
. And what part of the code will have to await
for that promise
to finish.
這種方法似乎是帶有承諾的生成器的混合體。 您只需要告訴您的代碼哪些函數將是async
。 以及代碼的哪一部分必須await
該promise
完成。
sumTwentyAfterTwoSeconds(10).then(result => console.log('after 2 seconds', result))async function sumTwentyAfterTwoSeconds(value) {const remainder = afterTwoSeconds(20)return value + await remainder
}function afterTwoSeconds(value) {return new Promise(resolve => {setTimeout(() => { resolve(value) }, 2000);});
}
In this scenario:
在這種情況下:
We have
sumTwentyAfterTwoSeconds
as being an async function我們將
sumTwentyAfterTwoSeconds
作為異步函數We tell our code to wait for the
resolve
orreject
for our promise functionafterTwoSeconds
在
afterTwoSeconds
我們告訴我們的代碼等待我們的promise函數的resolve
或reject
It will only end up in the
.then
when theawait
operations finish它只會在
await
操作結束時以.then
結尾In this case there is only one
在這種情況下,只有一個
Applying this to our request
we leave it as a promise
as seen earlier:
將此應用于我們的request
我們將其作為一個promise
如先前所示:
function request(url) {return new Promise(function(resolve, reject) {const xhr = new XMLHttpRequest();xhr.onreadystatechange = function(e) {if (xhr.readyState === 4) {if (xhr.status === 200) {resolve(xhr.response)} else {reject(xhr.status)}}}xhr.ontimeout = function () {reject('timeout')}xhr.open('get', url, true)xhr.send()})
}
We create our async
function with the needed awaits like so:
我們使用所需的等待創建async
函數,如下所示:
async function list() {const userGet = `https://api.github.com/search/users?page=1&q=daspinola&type=Users`const users = await request(userGet)const usersList = JSON.parse(users).itemsusersList.forEach(async function (user) {const repos = await request(user.repos_url)handleRepoList(user, repos)})
}function handleRepoList(user, repos) {const userRepos = JSON.parse(repos)// Handle each individual user repo hereconsole.log(user, userRepos)
}
So now we have an async list
function that will handle the requests. Another async is needed in the forEach
so that we have the list of repos
for each user to manipulate.
因此,現在我們有了一個異步list
函數來處理請求。 forEach
需要另一個異步,以便我們擁有供每個用戶操縱的存儲repos
列表。
We call it as:
我們稱其為:
list().catch(e => console.error(e))
This and the promises approach are my favorites since the code is easy to read and change. You can read about async/await more in depth here.
由于易于閱讀和更改代碼,因此我最喜歡這種方法和Promise方法。 您可以在此處詳細了解異步/等待。
A downside of using async/await is that it isn’t supported in the front-end by older browsers or in the back-end. You have to use the Node 8.
使用async / await的缺點是舊版瀏覽器或后端不支持它的前端。 您必須使用節點8。
You can use a compiler like babel to help solve that.
您可以使用像babel這樣的編譯器來解決這個問題。
“解” (“Solution”)
You can see the end code accomplishing our initial goal using async/await in this snippet.
您可以在此代碼段中看到使用async / await完成我們最初目標的最終代碼 。
A good thing to do is to try it yourself in the various forms referenced in this article.
要做的一件好事是以本文引用的各種形式自己嘗試。
結論 (Conclusion)
Depending on the scenario you might find yourself using:
根據情況,您可能會發現自己使用:
- async/await 異步/等待
- callbacks 回叫
- mix 混合
It’s up to you what fits your purposes. And what lets you maintain the code so that it is understandable to others and your future self.
取決于您的目的。 什么使您能夠維護代碼,以便他人和您將來的自己可以理解。
Note: Any of the approaches become slightly less verbose when using the alternatives for requests like $.ajax
and fetch
.
注意:當對$.ajax
和fetch
類的請求使用替代方法時,任何一種方法的冗長程度都會略微降低。
Let me know what you would do different and different ways you found to make each approach more readable.
讓我知道您會采取什么不同的方式來發現每種方法,以使其更具可讀性。
This is Article 11 of 30. It is part of a project for publishing an article at least once a week, from idle thoughts to tutorials. Leave a comment, follow me on Diogo Spínola and then go back to your brilliant project!
這是30條中的第11條。它是一個項目的一部分,該項目每周至少發表一次文章,從無聊的想法到教程。 發表評論,關注我DiogoSpínola ,然后回到您的杰出項目!
翻譯自: https://www.freecodecamp.org/news/javascript-from-callbacks-to-async-await-1cc090ddad99/