JS 异步(二)

事件循环和任务队列

eventLoop
在 ES6 中,有一个新的概念建立在事件循环队列之上,叫作任务队列(job queue)。这个
概念给大家带来的最大影响可能是 Promise 的异步特性(参见第 3 章).

事件循环队列类似于一个游乐园游戏:玩过了一个游戏之后,你需要重新到队尾排队才能 再玩一次。而任务队列类似于玩过了游戏之后,插队接着继续玩.

回调导致的信任问题

  • 回调未调用
  • 多次回调
  • 吞掉错误或异常
  • 等等

关于回调

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//这个叫做回调函数,可是并不异步
function useless(callback){
return callback();
}
useless(()=>{console.log("callback")});
(function fnA(){
console.log("fnA");
})()
//异步上的回调
document.body.addEvetListener("mousemove",function(){
//
})
//node的内部模块的异步回调惯例
const fs = require('fs');
fs.readFile('./title.json',(err,data)=>{
if(err) throw err;
//
})

交付第三方工具后控制反转

有时候 ajax(..)(也 就是你交付回调 continuation 的第三方)不是你编写的代码,也不在你的直接控制下。多 数情况下,它是某个第三方提供的工具。我们把这称为控制反转(inversion of control),也就是把自己程序一部分的执行控制交给某 个第三方。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//A
ajax('..',function(..){
//C
});
//B

//具体情景,
//var tracked = false;
analytics.trackPurchase(purchaseData, function(){
//if(!track)
chargeCreditCard();
displayThankyouPage();
});

//课栈遇到的回调未执行的情况……

分析公司的开发者开发了一些实验性的代码,在某种情况下,会在五秒钟内每秒重试一次传入的回调函数,然后才会因超时而失败。

异步开发的难题

异步控制

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
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
//手动维护一个任务队列的执行
//串行流程控制

//first task 1000
setTimeout(function() {
//second task 500
console.log('I execute first.');
setTimeout(function() {
console.log('I execute next.');
setTimeout(function() {
console.log('I execute last.');
}, 100);
}, 500);
}, 1000);

//并行流程控制,输出某些单词在整个文件目录下出现次数
var fs = require('fs');
var completedTasks = 0;
var tasks = [];
var wordCounts = {};
var filesDir = './text';
function checkIfComplete() {
completedTasks++;
if (completedTasks == tasks.length) {
for (var index in wordCounts) {
console.log(index +': ' + wordCounts[index]);
}
}
}
function countWordsInText(text) {
var words = text
.toString()
.toLowerCase()
.split(/\W+/)
.sort();
for (var index in words) {
var word = words[index];
if (word) {
wordCounts[word] =
(wordCounts[word]) ? wordCounts[word] + 1 : 1;
}
}
}
fs.readdir(filesDir, function(err, files) {
if (err) throw err;
for(var index in files) {
//用了闭包,冻结file[index]的值
var task = (function(file) {
return function() {
fs.readFile(file, function(err, text) {
if (err) throw err;
countWordsInText(text);
//每次完成后一个task,都进行任务检查
checkIfComplete();
});
}
})(filesDir + '/' + files[index]);
//依次添加至任务队列
tasks.push(task);
}
for(var task in tasks) {
//用循环执行,readfile是非阻塞I/O
tasks[task]();
}
});

Promise

promise

  • 特点
    • promise异步是基于微任务的 ->回调太晚(等待时间太长)
    • promise一旦决议,就永远保持这个状态
      • 决议机制 ->避免控制反转,回调未调用,回调多次
  • 实用
    • 链式回调 ->串行控制
    • promise.race ->可以用来处理一直未决议的状况
    • promise.all ->并行控制
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
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58

const promise = new Promise((resolve, reject) => {
resolve("Hattori");
setTimeout(()=> reject("Yoshi"), 500);
});
promise.then(val => alert("Success: " + val))
.catch(e => alert("Error: " + e));
//Success: Hattori

//实际开发
upload() {
return new Promise(resolve => {
Taro.uploadFile({
url: 'https://kstack.test.muxixyz.com/api/v1/upload/image/', //上传头像的服务器接口
filePath: this.state.avatar,
name: 'image',
formData: {
// image: this.state.file
},
header: {
token: Taro.getStorageSync('token')
// 'content-type': 'multipart/form-data'
},
success(res) {
if (res.data) {
resolve(JSON.parse(res.data).data.url);
}
}
});
});
}
onSubmit() {
if (this.state.username == '') {
Taro.atMessage({
message: '标题不能为空',
type: 'warning'
});
return;
}
this.upload()
.then(url => {
Fetch(
'api/v1/user/info/',
{
username: this.state.username,
avatar: url
},
'POST'
).then(ress => {
if (ress.message == 'OK')
Taro.showToast({ title: '修改成功', icon: 'success' });
});
})
.catch(err => {
console.error(err);
Taro.showToast({ title: '修改失败,请稍后重试', icon: 'fail' });
});
}

thenable

具有then()函数的对象,都是promise吗?不是。普通then()与promise产生的then的区别

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var p = {
then: function(cb,errb){
cb(42);
errb("evil laugh");
}
};
p.then(
function fulfilled(val){
console.log(val); //42
},
function rejected(err){
console.log(err); //evil laugh
//如果是真正promise的then方法,这两个是不会都运行的
}
)

Promise与生成器结合

生成器函数是一个崭新的函数类型,它和标准的普通函数完全不同。生成器函数能生成一组值的序列,但是每组值的生产都是基于每次请求,并不同于标准函数那样立即生成。

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
43
44
45
46
47
48
49
50
51
//能够暂停是由于函数上下文的保存
function *IdGenerator(){
let id = 0;
while(true){
yield ++id;
}
}
const idIterator = IdGenerator();
const ninja1 = { id: idIterator.next().value };
assert(ninja1.id === 1, "First ninja has id 1");

//combination
function getJSON(url){
return new Promise((resolve, reject) =>{
const request = new XMLHttpRequest();
request.open("GET", url);
request.send();
request.onload = function() {
try{
if(this.status === 200){
resolve(JSON.parse(this.response))
}
//else
} catch(e){
reject(e.message)
}
}
//request.onerror
})
}
async(function*() {
try {
const ninjas = yield getJSON("data/ninjas.json");
const missions = yield getJSON(ninjas[0].missionsUrl);
//All information recieved
}
catch(e) {
//An error has occurred
}
});
// ->
(async function () {
try {
const ninjas = await getJSON("data/ninjas.json");
const missions = await getJSON(missions[0].missionsUrl);
console.log(missions);
}
catch(e) {
console.log("Error: ", e);
}
})()

使用生成器与promise结合,串行化异步操作似乎比promise.then().then()这种链式回调更美观,可是我没有看到实质上不得不用asnyc,await的原因。之后再看看……

0%