In JavaScript, there is a super important concept called asynchronous, which is also the easiest concept to confuse and forget when you first start learning. ES6 natively supports Promise
, which works better with Generator
, and ES7 even supports the syntax of async
. I think this is an evolutionary process that makes the program architecture better and more readable. So to explain these new things, let’s start with the most basic callback.
Now let’s assume we have three APIs. The first is an API that fetches a list of articles.
[
{
"title": "Article 1",
"id": 1
},
{
"title": "Article 2",
"id": 2
},
{
"title": "Article 3",
"id": 3
}
]
The second is an API that fetches the content of an article given its ID.
{
"authorId": 5,
"content": "content",
"timestamp": "2015-08-26"
}
The third is an API that returns author information given an author ID.
{
"email": "[email protected]",
"name": "huli",
"id": 5
}
Now the functionality we want to achieve is: fetch the email of the author of the latest article. The process is: fetch the article list -> fetch the article information -> fetch the author. The code implementation looks like this.
getArticleList(function(articles){
getArticle(articles[0].id, function(article){
getAuthor(article.authorId, function(author){
alert(author.email);
})
})
})
function getAuthor(id, callback){
$.ajax("http://beta.json-generator.com/api/json/get/E105pDLh",{
author: id
}).done(function(result){
callback(result);
})
}
function getArticle(id, callback){
$.ajax("http://beta.json-generator.com/api/json/get/EkI02vUn",{
id: id
}).done(function(result){
callback(result);
})
}
function getArticleList(callback){
$.ajax(
"http://beta.json-generator.com/api/json/get/Ey8JqwIh")
.done(function(result){
callback(result);
});
}
Or refer to the online example: Implemented with callback
I believe that this code should not be unfamiliar to everyone, but there is a disadvantage to this approach, which is what we commonly call callback hell. It’s a bit ugly to have layer upon layer like this. So what should we do? There is something called Promise
, which appears like this. Let’s have a practical example first and then explain it!
getArticleList().then(function(articles){
return getArticle(articles[0].id);
}).then(function(article){
return getAuthor(article.authorId);
}).then(function(author){
alert(author.email);
});
function getAuthor(id){
return new Promise(function(resolve, reject){
$.ajax("http://beta.json-generator.com/api/json/get/E105pDLh",{
author: id
}).done(function(result){
resolve(result);
})
});
}
function getArticle(id){
return new Promise(function(resolve, reject){
$.ajax("http://beta.json-generator.com/api/json/get/EkI02vUn",{
id: id
}).done(function(result){
resolve(result);
})
});
}
function getArticleList(){
return new Promise(function(resolve, reject){
$.ajax(
"http://beta.json-generator.com/api/json/get/Ey8JqwIh")
.done(function(result){
resolve(result);
});
});
}
Online example: Implemented with PromisePromise
is an object with three states: pending, fulfilled, and rejected. In the above example, we removed the callback function of those three functions and replaced it with returning a Promise
object. The place where the callback should have appeared originally became resolve
. What are the benefits of doing this? Look at the place where we call these functions at the top. The original callback hell is gone, and we flattened it. If you don’t understand it very well, let’s start with the most basic, calling a Promise
.
getArticleList().then(function(articles){
console.log(articles);
});
function getArticleList(){
return new Promise(function(resolve, reject){
$.ajax(
"http://beta.json-generator.com/api/json/get/Ey8JqwIh")
.done(function(result){
resolve(result);
});
});
}
You can add .then
after a Promise object to get the result after the Promise is executed. If you return another Promise
object in then
, you can keep chaining them. For example:
getArticleList().then(function(articles){
return getArticle(articles[0].id);
}).then(function(article){
return getAuthor(article);
});
With this feature of Promise, you can avoid callback hell. If we add ES6 arrow functions, it can be simplified to:
getArticleList()
.then(articles => getArticle(articles[0].id))
.then(article => getAuthor(article.authorId))
.then(author => {
alert(author.email);
});
Online example of Promise+arrow function
The example of using Promise alone ends here. The syntax is already quite simple, and with arrow functions, it becomes more readable. However, seeing a bunch of then
can still be a bit annoying.
What’s next? ES6 has a new feature called Generator. If you don’t know what it is, you can refer to my previous article [Javascript] ES6 Generator Basics. Then we can use the feature of Generator to write code that looks super synchronous but is actually asynchronous:
function* run(){
var articles = yield getArticleList();
var article = yield getArticle(articles[0].id);
var author = yield getAuthor(article.authorId);
alert(author.email);
}
var gen = run();
gen.next().value.then(function(r1){
gen.next(r1).value.then(function(r2){
gen.next(r2).value.then(function(r3){
gen.next(r3);
console.log("done");
})
})
});
Complete online example of Promise + Generator
Looking closely at the run
generator, using the feature of yield
, the right-hand code will be executed first, waiting for the next call and assigning it to the left-hand side. So we can call gen.next(r1)
in the then
event of getArticleList()
, which will pass the return value to the articles
variable. If you find this a bit difficult to understand, you can start with a single layer:
function* run(){
var articles = yield getArticleList();
console.log(articles);
}
var gen = run();
// The first call will execute getArticleList(), which will return a Promise
gen.next().value.then(function(r1){
// After the first Promise is completed, pass r1 back to the generator to let articles = the return value of getArticleList()
gen.next(r1);
console.log('done');
});
Let’s take another look at the top half of the above code:
function* run(){
var articles = yield getArticleList();
var article = yield getArticle(articles[0].id);
var author = yield getAuthor(article.authorId);
alert(author.email);
}
Do you feel that it looks very similar to synchronous code? If you remove yield
, it is exactly the same! This is the essence of Generator: using syntax that looks very similar to synchronous code, but is actually asynchronous.
Let’s take a look at the second half:
var gen = run();
gen.next().value.then(function(r1){
gen.next(r1).value.then(function(r2){
gen.next(r2).value.then(function(r3){
gen.next(r3);
console.log("done");
})
})
});
It is easy to see that the syntax of the second half is very fixed and easy to find patterns. It is also a recursion. Therefore, it can be wrapped in a function to handle more general cases.
function* run(){
var articles = yield getArticleList();
var article = yield getArticle(articles[0].id);
var author = yield getAuthor(article.authorId);
alert(author.email);
}
function runGenerator(){
var gen = run();
function go(result){
if(result.done) return;
result.value.then(function(r){
go(gen.next(r));
});
}
go(gen.next());
}
runGenerator();
Complete online example Promise + Generator + Recursion
The co
module made by tj
is doing almost the same thing, but it does more. The principle is similar to what we wrote above, which is to wrap a generator and write an automatic executor.
Finally, let’s talk about the last thing in the title: async
. What is it? Let’s take a look at the code:
async function run(){
var articles = await getArticleList();
var article = await getArticle(articles[0].id);
var author = await getAuthor(article.authorId);
alert(author.email);
}
Complete online example async (cannot run)
The difference between this code and the previous one is that:
function* gen()
becomesasync function run()
yield
becomesawait
That’s it. And you will find that it ends like this. You don’t need to use other modules or write your own recursive executor. This is the syntax of async
, which is just to write those automatic executors, but this syntax makes it much more convenient for us. Actually, this syntax is planned to be introduced in ES7
, QQ.
The good news is that the ES6 code we wrote above has been converted to ES5 syntax through the babel
library. And it has an experimental feature, which includes async
. And async
is in stage 2
, NOTE: Stage 2 and above are enabled by default. You don’t need to adjust any parameters to automatically enable it, which is really gratifying.
When I first came into contact with ES6, I was overwhelmed by a lot of dazzling things. Each one is a subject to delve into. And I used pure callbacks before (because the level is not much, so it’s okay), and occasionally used async
(node’s library, different from the one above). So I think the best way to understand it is to start with the most basic callback, gradually progress to promise, then to generator, and finally to async. Only then can we understand why these things appear.
If there are any mistakes in the above, please leave a message or send me an email. Thank you.
Ref:
ECMAScript 6 入门 异步操作
JavaScript Promises
拥抱Generator,告别异步回调
深入浅出ES6(三):生成器 Generators
Comments