JavaScript异步编程的机制和相关编程技巧

JavaScript异步编程的机制和相关编程技巧

JavaScript异步编程机制

JavaScript异步编程机制简单来说就是 callback 机制实现的效果。callback形式的代码写复杂的异步编程,很容易出现callback地狱的问题,不利于代码的维护和管理,而Promise和es6的async可以很好的解决这个问题,使得写异步编程编的非常简单。

Promise是什么

简单来说,Promise 主要就是为了解决异步回调的问题。用 Promise 来处理异步回调使得代码层次清晰,便于理解,且更加容易维护。其主流规范目前主要是 Promises/A+
Promise 原理大概就类似这个样子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// demo.1.js
function Promise(fn) {
let callback;
let rejectCallback;
//一个实例的方法,用来注册异步事件
this.then = function (done, errCallback) {
callback = done;
rejectCallback = errCallback;
}
function resolve() {
callback();
}
try {
fn(resolve);
} catch (e) {
rejectCallback(e);
}
}

使用Promise

1
2
3
4
5
6
7
8
9
10
11
12
13
function timeout3(callback) {
setTimeout(() => {
callback('abcd')
}, 3000)
}

new Promise(timeout3).then((data) => {
console.log(3)
});

// # console:
// 3 (三秒后屏幕出现 )

这里只是简单的模拟一下Promise的代码,还有一下Promise的api没有全部实现,可以去深入研究Promise的源码,搞懂 Promise的 实现原理,能改使用Promise编程更加得心应手的.

使用Promise封装 node 的 http.request方法,实现简单的 fetch api 效果

封装fetch API

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
31
32
33
34
35
36
37
38
39
40
41
42
// demo.2.js

const http=require('http');

const fetch = (url,fetchOptions = {}) =>{
return new Promise((resolve, reject)=>{
var match = /((http|ftp|https):\/\/)((([a-zA-Z0-9\._-]+\.[a-zA-Z]{2,6})|([0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}))(:[0-9]{1,4})*(\/[a-zA-Z0-9\&\%_\.\/-~-]*)?)/;
const matchObj = match.exec(url);
if(!matchObj){
reject({
message: url+'域名格式不正确',
});
}
const [,,,,,hostname,,,,path] = matchObj;
var options = {
"method": "GET",
hostname,
path,
};
var req = http.request(options, function (res) {
var chunks = [];

res.on("data", function (chunk) {
chunks.push(chunk);
});

res.on('error', (e) => {
reject(e);
})

res.on("end", function () {
var body = Buffer.concat(chunks);
resolve({
json:()=>Promise.resolve(JSON.parse(body.toString())),
blob: ()=>Promise.resolve(body),
text: ()=>Promise.resolve(body.toString()),
});
});
});
req.end();
});
}

简单的调用fetch

1
2
3
4
5
6
fetch('http://www.mmjpg.com/')
.then(data=>data.text())
.then(str=>{
// 输出网页源码
console.log(str);
})

fetch链式调用

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
// demo.3.js

const fetch = require('./demo.2');

fetch('http://www.mmjpg.com/')
.then(data => data.text())
.then(content => {
const url = /(http:\/\/www.mmjpg.com\/mm\/\d+)/.exec(content)[1];
return fetch(url).then(data=>data.text())
.then(data => {
const n = /var\s*picinfo\s*=\s*\[(\d+),(\d+),(\d+),(\d+)\]/.exec(data)[3];
console.log(n);
return Array.from({length: n}).map((item,index)=>{
return url+'/'+(index+1);
});
});
})
.then(imageUrls => {
console.log(imageUrls);
})

// # console:
// 60
// [ 'http://www.mmjpg.com/mm/1355/1',
// ...
// ...
// 'http://www.mmjpg.com/mm/1355/59',
// 'http://www.mmjpg.com/mm/1355/60' ]

fetch按顺序批量获取url内容

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
31
32
33
34
35
// demo.4.js

const fetch = require('./demo.2');
const imgUrls = ['http://www.mmjpg.com/mm/1355/1', 'http://www.mmjpg.com/mm/1355/2', 'http://www.mmjpg.com/mm/1355/3', 'http://www.mmjpg.com/mm/1355/4', 'http://www.mmjpg.com/mm/1355/7', 'http://www.mmjpg.com/mm/1355/8', 'http://www.mmjpg.com/mm/1355/57', 'http://www.mmjpg.com/mm/1355/58', 'http://www.mmjpg.com/mm/1355/59', 'http://www.mmjpg.com/mm/1355/60'];


const fetchImg = (urls, index=0, currentResult = []) => {
const url = urls[index];
if (url) {
return fetch(url)
.then(data => data.blob())
.then(buffer => {
console.log('Progress: ' + index + '/' + urls.length)
return fetchImg(urls, index + 1, [...currentResult, buffer])
})
} else {
return Promise.resolve(currentResult);
}
}

fetchImg(imgUrls)
.then(bufferList => {
// save imgBuffer to file...
console.log(bufferList.length);
})

// console:
/*
Progress: 0/60
Progress: 1/60
...
...
Progress: 58/60
Progress: 59/60
*/

使用异步编程Promise.all优化后的效果

1
2
3
4
5
6
7
8
9
10
11
12
13
// demo.5.js

const fetch = require('./demo.2');
const imgUrls = ['http://www.mmjpg.com/mm/1355/1', 'http://www.mmjpg.com/mm/1355/2', 'http://www.mmjpg.com/mm/1355/3', 'http://www.mmjpg.com/mm/1355/4', 'http://www.mmjpg.com/mm/1355/5', 'http://www.mmjpg.com/mm/1355/6', 'http://www.mmjpg.com/mm/1355/57', 'http://www.mmjpg.com/mm/1355/58', 'http://www.mmjpg.com/mm/1355/59', 'http://www.mmjpg.com/mm/1355/60'];


Promise.all(imgUrls.map(url=>fetch(url).then(data=>data.blob())))
.then(bufferList=> {
// save imgBuffer to file...
console.log(bufferList.length);
});


es6中async使用 Promise.all

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

// demo.6.js

const fetch = require('./demo.2');
const imgUrls = ['http://www.mmjpg.com/mm/1355/1', 'http://www.mmjpg.com/mm/1355/2', 'http://www.mmjpg.com/mm/1355/3', 'http://www.mmjpg.com/mm/1355/4', 'http://www.mmjpg.com/mm/1355/5', 'http://www.mmjpg.com/mm/1355/6', 'http://www.mmjpg.com/mm/1355/7', 'http://www.mmjpg.com/mm/1355/8', 'http://www.mmjpg.com/mm/1355/9', 'http://www.mmjpg.com/mm/1355/10', 'http://www.mmjpg.com/mm/1355/11', 'http://www.mmjpg.com/mm/1355/12', 'http://www.mmjpg.com/mm/1355/13', 'http://www.mmjpg.com/mm/1355/14', 'http://www.mmjpg.com/mm/1355/15', 'http://www.mmjpg.com/mm/1355/16', 'http://www.mmjpg.com/mm/1355/17', 'http://www.mmjpg.com/mm/1355/18', 'http://www.mmjpg.com/mm/1355/19', 'http://www.mmjpg.com/mm/1355/20', 'http://www.mmjpg.com/mm/1355/21', 'http://www.mmjpg.com/mm/1355/22', 'http://www.mmjpg.com/mm/1355/23', 'http://www.mmjpg.com/mm/1355/24', 'http://www.mmjpg.com/mm/1355/25', 'http://www.mmjpg.com/mm/1355/26', 'http://www.mmjpg.com/mm/1355/27', 'http://www.mmjpg.com/mm/1355/28', 'http://www.mmjpg.com/mm/1355/29', 'http://www.mmjpg.com/mm/1355/30', 'http://www.mmjpg.com/mm/1355/31', 'http://www.mmjpg.com/mm/1355/32', 'http://www.mmjpg.com/mm/1355/33', 'http://www.mmjpg.com/mm/1355/34', 'http://www.mmjpg.com/mm/1355/35', 'http://www.mmjpg.com/mm/1355/36', 'http://www.mmjpg.com/mm/1355/37', 'http://www.mmjpg.com/mm/1355/38', 'http://www.mmjpg.com/mm/1355/39', 'http://www.mmjpg.com/mm/1355/40', 'http://www.mmjpg.com/mm/1355/41', 'http://www.mmjpg.com/mm/1355/42', 'http://www.mmjpg.com/mm/1355/43', 'http://www.mmjpg.com/mm/1355/44', 'http://www.mmjpg.com/mm/1355/45', 'http://www.mmjpg.com/mm/1355/46', 'http://www.mmjpg.com/mm/1355/47', 'http://www.mmjpg.com/mm/1355/48', 'http://www.mmjpg.com/mm/1355/49', 'http://www.mmjpg.com/mm/1355/50', 'http://www.mmjpg.com/mm/1355/51', 'http://www.mmjpg.com/mm/1355/52', 'http://www.mmjpg.com/mm/1355/53', 'http://www.mmjpg.com/mm/1355/54', 'http://www.mmjpg.com/mm/1355/55', 'http://www.mmjpg.com/mm/1355/56', 'http://www.mmjpg.com/mm/1355/57', 'http://www.mmjpg.com/mm/1355/58', 'http://www.mmjpg.com/mm/1355/59', 'http://www.mmjpg.com/mm/1355/60'];

const fetAllData = async () => {
const bufferList = await Promise.all(imgUrls.map(url=>fetch(url).then(data=>data.blob())));
// save imgBuffer to file...
console.log(bufferList.lenght);
}

fetAllData();