Skip to content

Commit c59eef1

Browse files
popomoredead-horse
authored andcommitted
feat: async/await Example (#12)
1 parent 0087214 commit c59eef1

File tree

19 files changed

+626
-0
lines changed

19 files changed

+626
-0
lines changed

hackernews-async/.autod.conf.js

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
'ues strict';
2+
3+
module.exports = {
4+
write: true,
5+
plugin: 'autod-egg',
6+
prefix: '^',
7+
devprefix: '^',
8+
registry: 'https://r.cnpmjs.org',
9+
exclude: [
10+
'test/fixtures',
11+
],
12+
dep: [
13+
'egg',
14+
],
15+
devdep: [
16+
'autod',
17+
'autod-egg',
18+
'eslint',
19+
'eslint-config-egg',
20+
'egg-bin',
21+
],
22+
keep: [
23+
],
24+
semver: [
25+
],
26+
test: 'scripts',
27+
};

hackernews-async/.eslintrc

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"parserOptions": {
3+
"ecmaVersion": 2017
4+
}
5+
}

hackernews-async/README.md

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
# egg-example-hackernews-async
2+
3+
[Hacker News](https://news.ycombinator.com/) showcase using async/await for egg
4+
5+
## QuickStart
6+
7+
### Development
8+
```shell
9+
$ npm install
10+
$ npm run dev
11+
$ open http://localhost:7001/
12+
```
13+
14+
### Deploy
15+
16+
Use `EGG_SERVER_ENV=prod` to enable prod mode
17+
18+
```shell
19+
$ EGG_SERVER_ENV=prod npm start
20+
```
21+
22+
### Npm Scripts
23+
24+
- Use `npm run autod` to auto detect dependencies upgrade
25+
- Use `npm run lint` to check code style
26+
- Use `npm test` to run unit test
27+
28+
### Requirement
29+
30+
Please ensure your node version is `>=7.6.0` for async await support without flag. If your node version is `>=7.0.0 < 7.6.0`, you can run npm scripts with harmony flag
31+
32+
```shell
33+
# start server
34+
npm run dev -- --harmony-async-await
35+
# run test cases
36+
npm run test-local -- --harmony-async-await
37+
```
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
'use strict';
2+
3+
module.exports = app => {
4+
return class NewsController extends app.Controller {
5+
async list() {
6+
const { ctx, app } = this;
7+
const pageSize = app.config.news.pageSize;
8+
const page = parseInt(ctx.query.page) || 1;
9+
10+
const idList = await ctx.service.hackerNews.getTopStories(page);
11+
12+
// get itemInfo parallel
13+
const newsList = await Promise.all(idList.map(id => ctx.service.hackerNews.getItem(id)));
14+
await ctx.render('news/list.tpl', { list: newsList, page, pageSize });
15+
}
16+
17+
async detail() {
18+
const { ctx } = this;
19+
const id = ctx.params.id;
20+
const newsInfo = await ctx.service.hackerNews.getItem(id);
21+
// get comment parallel
22+
const commentList = await Promise.all(newsInfo.kids.map(id => ctx.service.hackerNews.getItem(id)));
23+
await ctx.render('news/detail.tpl', { item: newsInfo, comments: commentList });
24+
}
25+
26+
async user() {
27+
const { ctx } = this;
28+
const id = ctx.params.id;
29+
const userInfo = await ctx.service.hackerNews.getUser(id);
30+
await ctx.render('news/user.tpl', { user: userInfo });
31+
}
32+
};
33+
};
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
'use strict';
2+
3+
const moment = require('moment');
4+
5+
exports.relativeTime = time => moment(new Date(time * 1000)).fromNow();
6+
7+
exports.domain = url => url && url.split('/')[2];
Lines changed: 203 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,203 @@
1+
body,
2+
html {
3+
font-family: Verdana;
4+
font-size: 13px;
5+
height: 100%
6+
}
7+
ul {
8+
list-style-type: none;
9+
padding: 0;
10+
margin: 0
11+
}
12+
a {
13+
color: #000;
14+
cursor: pointer;
15+
text-decoration: none
16+
}
17+
#wrapper {
18+
background-color: #f6f6ef;
19+
width: 85%;
20+
min-height: 80px;
21+
margin: 0 auto
22+
}
23+
#header,
24+
#wrapper {
25+
position: relative
26+
}
27+
#header {
28+
background-color: #f60;
29+
height: 24px
30+
}
31+
#header h1 {
32+
font-weight: 700;
33+
font-size: 13px;
34+
display: inline-block;
35+
vertical-align: middle;
36+
margin: 0
37+
}
38+
#header .source {
39+
color: #fff;
40+
font-size: 11px;
41+
position: absolute;
42+
top: 4px;
43+
right: 4px
44+
}
45+
#header .source a {
46+
color: #fff
47+
}
48+
#header .source a:hover {
49+
text-decoration: underline
50+
}
51+
#yc {
52+
border: 1px solid #fff;
53+
margin: 2px;
54+
display: inline-block
55+
}
56+
#yc,
57+
#yc img {
58+
vertical-align: middle
59+
}
60+
.view {
61+
position: absolute;
62+
background-color: #f6f6ef;
63+
width: 100%;
64+
-webkit-transition: opacity .2s ease;
65+
transition: opacity .2s ease;
66+
box-sizing: border-box;
67+
padding: 8px 20px
68+
}
69+
.view.v-enter,
70+
.view.v-leave {
71+
opacity: 0
72+
}
73+
@media screen and (max-width: 700px) {
74+
body,
75+
html {
76+
margin: 0
77+
}
78+
#wrapper {
79+
width: 100%
80+
}
81+
}
82+
.news-view {
83+
padding-left: 5px;
84+
padding-right: 15px
85+
}
86+
.news-view.loading:before {
87+
content: "Loading...";
88+
position: absolute;
89+
top: 16px;
90+
left: 20px
91+
}
92+
.news-view .nav {
93+
padding: 10px 10px 10px 40px;
94+
margin-top: 10px;
95+
border-top: 2px solid #f60
96+
}
97+
.news-view .nav a {
98+
margin-right: 10px
99+
}
100+
.news-view .nav a:hover {
101+
text-decoration: underline
102+
}
103+
.item {
104+
padding: 2px 0 2px 40px;
105+
position: relative;
106+
-webkit-transition: background-color .2s ease;
107+
transition: background-color .2s ease
108+
}
109+
.item p {
110+
margin: 2px 0
111+
}
112+
.item .index,
113+
.item .title:visited {
114+
color: #828282
115+
}
116+
.item .index {
117+
position: absolute;
118+
width: 30px;
119+
text-align: right;
120+
left: 0;
121+
top: 4px
122+
}
123+
.item .domain,
124+
.item .subtext {
125+
font-size: 11px;
126+
color: #828282
127+
}
128+
.item .domain a,
129+
.item .subtext a {
130+
color: #828282
131+
}
132+
.item .subtext a:hover {
133+
text-decoration: underline
134+
}
135+
.item-view .item {
136+
padding-left: 0;
137+
margin-bottom: 30px
138+
}
139+
.item-view .item .index {
140+
display: none
141+
}
142+
.item-view .poll-options {
143+
margin-left: 30px;
144+
margin-bottom: 40px
145+
}
146+
.item-view .poll-options li {
147+
margin: 12px 0
148+
}
149+
.item-view .poll-options p {
150+
margin: 8px 0
151+
}
152+
.item-view .poll-options .subtext {
153+
color: #828282;
154+
font-size: 11px
155+
}
156+
.item-view .itemtext {
157+
color: #828282;
158+
margin-top: 0;
159+
margin-bottom: 30px
160+
}
161+
.item-view .itemtext p {
162+
margin: 10px 0
163+
}
164+
.comhead {
165+
font-size: 11px;
166+
margin-bottom: 8px
167+
}
168+
.comhead,
169+
.comhead a {
170+
color: #828282
171+
}
172+
.comhead a:hover {
173+
text-decoration: underline
174+
}
175+
.comhead .toggle {
176+
margin-right: 4px
177+
}
178+
.comment-content {
179+
margin: 0 0 16px 24px;
180+
word-wrap: break-word
181+
}
182+
.comment-content code {
183+
white-space: pre-wrap
184+
}
185+
.child-comments {
186+
margin: 8px 0 8px 22px
187+
}
188+
.user-view {
189+
color: #828282
190+
}
191+
.user-view li {
192+
margin: 5px 0
193+
}
194+
.user-view .label {
195+
display: inline-block;
196+
min-width: 60px
197+
}
198+
.user-view .about {
199+
margin-top: 1em
200+
}
201+
.user-view .links a {
202+
text-decoration: underline
203+
}
3.04 KB
Loading

hackernews-async/app/router.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
'use strict';
2+
3+
module.exports = app => {
4+
app.redirect('/', '/news');
5+
app.get('/news', 'news.list');
6+
app.get('/news/item/:id', 'news.detail');
7+
app.get('/news/user/:id', 'news.user');
8+
};
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
'use strict';
2+
3+
module.exports = app => {
4+
/**
5+
* HackerNews Api Service
6+
*/
7+
class HackerNews extends app.Service {
8+
constructor(ctx) {
9+
super(ctx);
10+
this.config = this.ctx.app.config.news;
11+
this.serverUrl = this.config.serverUrl;
12+
this.pageSize = this.config.pageSize;
13+
}
14+
15+
/**
16+
* request hacker-news api
17+
* @param {String} api - Api name
18+
* @param {Object} [opts] - urllib options
19+
* @return {Promise} response.data
20+
*/
21+
async request(api, opts) {
22+
const options = Object.assign({
23+
dataType: 'json',
24+
}, opts);
25+
26+
const result = await this.ctx.curl(`${this.serverUrl}/${api}`, options);
27+
return result.data;
28+
}
29+
30+
/**
31+
* get top story ids
32+
* @param {Number} [page] - page number, 1-base
33+
* @param {Number} [pageSize] - page count
34+
* @return {Promise} id list
35+
*/
36+
async getTopStories(page, pageSize) {
37+
page = page || 1;
38+
pageSize = pageSize || this.pageSize;
39+
40+
const result = await this.request('topstories.json', {
41+
data: {
42+
orderBy: '"$key"',
43+
startAt: `"${pageSize * (page - 1)}"`,
44+
endAt: `"${pageSize * page - 1}"`,
45+
},
46+
});
47+
return Object.keys(result).map(key => result[key]);
48+
}
49+
50+
/**
51+
* query item
52+
* @param {Number} id - itemId
53+
* @return {Promise} item info
54+
*/
55+
async getItem(id) {
56+
return await this.request(`item/${id}.json`);
57+
}
58+
59+
/**
60+
* get user info
61+
* @param {Number} id - userId
62+
* @return {Promise} user info
63+
*/
64+
async getUser(id) {
65+
return await this.request(`user/${id}.json`);
66+
}
67+
}
68+
69+
return HackerNews;
70+
};

0 commit comments

Comments
 (0)