es6异步编程篇

Genretor篇

运用场景

(1) 一个抽奖的小栗子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
function draw(count) {
// 执行一段抽奖逻辑
// ...
console.log(`剩余${count}次`)
}

// 执行抽奖的方法
function* remain(count) {
while(count > 0) {
count--
yield draw(count)
}
}

let startDrawing = remain(6)

let btn = document.createElement('button')
btn.id = 'start'
btn.textContent = '开始抽奖'
document.body.appendChild(btn)

document.getElementById('start').addEventListener('click', function(){
startDrawing.next()
}, false);

(2)长轮询

实现实时把服务器数据更新到客户端

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// 模拟一个请求
function* ajax() {
yield new Promise((resolve, reject) => {
// 此处用一个定时器来模拟请求数据的耗时,并约定当返回的json中code为0表示有新数据更新
setTimeout(() => {
resolve({code: 0})
}, 200)
})
}

function update() {
let promise = ajax().next().value;
promise.then(res => {
if(res.code !== 0) {
setTimeout(() => {
console.log("2秒后继续查询...")
update();
}, 2000);
} else {
console.log(res);
}
});
}

update();

(3) 部署ajax, 实现同步

异步函数的同步表达

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function* main() {
let result = yield request("http://some.url");
let resp = JSON.parse(result);
console.log(resp.value)
}

let it = main();
it.next();

function request(url) {
axio({
// ...
}).then(res => {
it.next(res.data);
});
}

(4) 部署iterator接口

1
2
3
4
5
6
7
8
9
10
const o1 = {
a: 1,
b: 2,
c: 3,
[Symbol.iterator]: function* () {
for(let key in this) {
yield this[key];
}
}
}

自动执行

Thunk函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 编译器实现"传名调用"
function f(m) {
return m * 2;
}

f(x + 5);

// 等同于
var thunk = function () {
return x + 5;
};

function f(thunk) {
return thunk() * 2;
}

JavaScriptThunk 函数

JavaScript采取的是传值调用, thunk函数含义有所不同

1
2
3
4
5
6
7
8
9
10
11
12
// 简单实现
const Thunk = function(fn) {
return function (...args) {
return function (callback) {
return fn.call(this, ...args, callback);
}
};
};

// 使用
var readFileThunk = Thunk(fs.readFile);
readFileThunk(fileA)(callback);

生产环境的Thunk转换器

1
2
// 安装
$ cnpm install thunkify

Thunk实现自动执行

1
2
3
4
5
6
7
8
9
// 栗子
var g = function* (){
var f1 = yield readFileThunk('fileA');
var f2 = yield readFileThunk('fileB');
// ...
var fn = yield readFileThunk('fileN');
};

run(g);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function run(fn) {
var gen = fn();
// next就是异步任务的回调
function next(err, data) {
var result = gen.next(data);
if (result.done) return;
result.value(next);
}

next();
}

function* g() {
// ...
}

run(g);

初看上面代码会懵逼的

1
2
3
4
5
6
7
8
9
10
11
12
// 先理解手动执行
var g = gen();

var r1 = g.next();
r1.value(function (err, data) {
if (err) throw err;
var r2 = g.next(data);
r2.value(function (err, data) {
if (err) throw err;
g.next(data);
});
});

async篇

ES2017标准,Generator 函数的语法糖,自带执行器

运用场景

(1) 一个请求接着一个请求

1
2
3
4
5
6
7
8
// 后一个请求依赖前一个请求
// 图片处理完成然后保存在本地

async function fn1() {
const base64Data = await download('http://1.jpg');
const result = await saveToLocal(base64Data);
return result;
}

(2) 并发任务, 但是同步处理结果(处理任务是异步)

1
2
3
4
5
6
7
8
9
async function fn2() {

const promise1 = convertToBase64Data("url1");
const promise2 = convertToBase64Data("url2");
const [data1, data2] = await Promise.all([promise1, promise2]);

const result = await saveToLocal(data1);
const result2 = await saveToLocal(data2);
}

(3)多张图片,处理一张保存一张,然后才能处理下一张

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// 无await, 就自己瞎写
const imgUrls = ['http://1.jpg', 'http://2.jpg', 'http://3.jpg', 'http://4.jpg', 'http://5.jpg'];
let i = 0;
function next(result) {

if (result) {
saveToLocal(result).then(res => {
console.log(res)
});
console.log(result)
}

if (i < imgUrls.length) {
convertToBase64Data(imgUrls[i++]).then(next);
}
}

next();

// 还是await舒服...
(async () => {

for (let i = 0; i < imgUrls.length; i++) {

let data = await convertToBase64Data(imgUrls[i]);
let result = await saveToLocal(data);
}
})();

(4)错误处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// 无await
function fn6() {
try {
convertToBase64('http://1.jpg').then( data => {

saveToLocal(data).then( result => {
console.log(result);
});
// .catch(err => { console.log(err)}); // 只能在.catch中处理
});
} catch (err) {
// 这里捕获不了saveToLocal的错误
console.log(err);
}
}

// await处理错误
async function fn6() {
try {
const data = await convertToBase64('http://1.jpg');
const result = await saveToLocal(data);
console.log(result);
} catch (err) {
console.log(err);
}
}

(5)超时处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function timeout(delay) {

return new Promise((res, rej) => {
setTimeout(() => {
rej(new Error("不用等了, 别傻了"));
}, delay);
});
}

async function imageCrawler(url, delay) {

try {
let img = await Promise.race([getImage(url), timeout(delay)]);
return img;
} catch (error) {
console.log(error)
}
}

(6)并发限制

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
async function getImages(urls, limit) {
let running = 0;
let r;
let p = new Promise((resolve, reject) => {
r = resolve
});

function run() {
if (running < limit && urls.length > 0) {
running++;
let url = urls.shift();
// 这里要注意, async是异步
(async () => {
let img = await getImage(url);
running--;
console.log(img)
if (urls.length === 0 && running === 0) {
console.log("done")
return r("done");
} else {
run();
}
})();
// 立即到并发上限
run();
}
}
run();
return await p;
}

这里补充一下基础知识

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
(async () => {
console.log("wochhh")
console.log("wochhh")
console.log("wochhh")
let img = await convertToBase64Data("...");
console.log(img)
console.log("?2")
console.log("?2")
console.log("?2")
})();
console.log("asdgfasgd");
console.log("asdgfasgd");
console.log("asdgfasgd");

// 输出
wochhh
wochhh
wochhh
asdgfasgd
asdgfasgd
asdgfasgd
?2
?2
?2
-------------要说再见啦感谢大佬的光临~-------------