JiM-W

keep Moving


  • 首页

  • 分类

  • 关于

  • 归档

  • 标签

fetch请求(promise的封装)

发表于 2017-06-20 | 分类于 ES6

1 一般的fetch用法

1
2
3
4
5
fetch(url,option).then(response=>{
//handle HTTP response
}).then(error=>{
//handle HTTP error
})

具体的栗子如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
fetch(url, { //option
method: "POST",
body: JSON.stringify(data),
headers: {
"Content-Type": "application/json"
},
credentials: "same-origin"
}).then(function(response) {
response.status //=> number 100–599
response.statusText //=> String
response.headers //=> Headers
response.url //=> String
return response.text()
}, function(error) {
error.message //=> String
})

2 参数解析

url 地址: ‘./path’

option :

  • method (String) - HTTP request method. Default: "GET"
  • body (String, body types) - HTTP request body
  • headers (Object, Headers) - Default: {}
  • credentials (String) - Authentication credentials mode. Default: "omit"``"omit" - don’t include authentication credentials (e.g. cookies) in the request"same-origin" - include credentials in requests to the same site"include" - include credentials in requests to all sites

Response

Response represents a HTTP response from the server. Typically a Response is not constructed manually, but is available as argument to the resolved promise callback.

Properties

  • status (number) - HTTP response code in the 100–599 range
  • statusText (String) - Status text as reported by the server, e.g. “Unauthorized”
  • ok (boolean) - True if status is HTTP 2xx
  • headers (Headers)
  • url (String)

Body methods 注意每个方法返回的都是一个Promise对象,

Each of the methods to access the response body returns a Promise that will be resolved when the associated data type is ready.

  • text() - yields the response text as String
  • json() - yields the result of JSON.parse(responseText)
  • blob() - yields a Blob
  • arrayBuffer() - yields an ArrayBuffer
  • formData() - yields FormData that can be forwarded to another request

Other response methods

  • clone()
  • Response.error()
  • Response.redirect()

ES6-Promise源码分析

发表于 2017-06-15 | 分类于 ES6

转载自: http://mp.weixin.qq.com/s/gDCPwMnKkAgbFWo6R6PRpw

前言**

前一阵子记录了promise的一些常规用法,这篇文章再深入一个层次,来分析分析promise的这种规则机制是如何实现的。ps:本文适合已经对promise的用法有所了解的人阅读,如果对其用法还不是太了解,可以移步我的上一篇博文。

本文的promise源码是按照Promise/A+规范来编写的(不想看英文版的移步Promise/A+规范中文翻译)

引子

为了让大家更容易理解,我们从一个场景开始讲解,让大家一步一步跟着思路思考,相信你一定会更容易看懂。

考虑下面一种获取用户id的请求处理

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
//例1
function getUserId() {
return new Promise(function(resolve) {
//异步请求
http.get(url, function(results) {
resolve(results.id)
})
})
}
getUserId().then(function(id) {
//一些处理
})

getUserId方法返回一个promise,可以通过它的then方法注册(注意注册这个词)在promise异步操作成功时执行的回调。这种执行方式,使得异步调用变得十分顺手。

原理剖析

那么类似这种功能的Promise怎么实现呢?其实按照上面一句话,实现一个最基础的雏形还是很easy的。

极简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
function Promise(fn) {
var value = null,
callbacks = []; //callbacks为数组,因为可能同时有很多个回调
this.then = function (onFulfilled) {
callbacks.push(onFulfilled);
};
function resolve(value) {
callbacks.forEach(function (callback) {
callback(value);
});
}
fn(resolve);
}

上述代码很简单,大致的逻辑是这样的:

  1. 调用then方法,将想要在Promise异步操作成功时执行的回调放入callbacks队列,其实也就是注册回调函数,可以向观察者模式方向思考;
  2. 创建Promise实例时传入的函数会被赋予一个函数类型的参数,即resolve,它接收一个参数value,代表异步操作返回的结果,当一步操作执行成功后,用户会调用resolve方法,这时候其实真正执行的操作是将callbacks队列中的回调一一执行;

可以结合例1中的代码来看,首先new Promise时,传给promise的函数发送异步请求,接着调用promise对象的then属性,注册请求成功的回调函数,然后当异步请求发送成功时,调用resolve(results.id)方法, 该方法执行then方法注册的回调数组。

相信仔细的人应该可以看出来,then方法应该能够链式调用,但是上面的最基础简单的版本显然无法支持链式调用。想让then方法支持链式调用,其实也是很简单的:

this.then = function (onFulfilled) {

​ callbacks.push(onFulfilled);

​ return this;

};

see?只要简单一句话就可以实现类似下面的链式调用:

// 例2

getUserId().then(function (id) {

​ // 一些处理

}).then(function (id) {

​ // 一些处理

});

加入延时机制

细心的同学应该发现,上述代码可能还存在一个问题:如果在then方法注册回调之前,resolve函数就执行了,怎么办?比如promise内部的函数是同步函数:

// 例3

function getUserId() {

​ return new Promise(function (resolve) {

​ resolve(9876);

​ });

}

getUserId().then(function (id) {

​ // 一些处理

});

这显然是不允许的,Promises/A+规范明确要求回调需要通过异步方式执行,用以保证一致可靠的执行顺序。因此我们要加入一些处理,保证在resolve执行之前,then方法已经注册完所有的回调。我们可以这样改造下resolve函数:

function resolve(value) {

​ setTimeout(function() {

​ callbacks.forEach(function (callback) {

​ callback(value);

​ });

​ }, 0)

}

上述代码的思路也很简单,就是通过setTimeout机制,将resolve中执行回调的逻辑放置到JS任务队列末尾,以保证在resolve执行时,then方法的回调函数已经注册完成.

但是,这样好像还存在一个问题,可以细想一下:如果Promise异步操作已经成功,这时,在异步操作成功之前注册的回调都会执行,但是在Promise异步操作成功这之后调用的then注册的回调就再也不会执行了,这显然不是我们想要的。

加入状态

恩,为了解决上一节抛出的问题,我们必须加入状态机制,也就是大家熟知的pending、fulfilled、rejected。

Promises/A+规范中的2.1Promise States中明确规定了,pending可以转化为fulfilled或rejected并且只能转化一次,也就是说如果pending转化到fulfilled状态,那么就不能再转化到rejected。并且fulfilled和rejected状态只能由pending转化而来,两者之间不能互相转换。一图胜千言:

img

改进后的代码是这样的:

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 Promise(fn) {
var state = 'pending',
value = null,
callbacks = [];
this.then = function (onFulfilled) {
if (state === 'pending') {
callbacks.push(onFulfilled);
return this;
}
onFulfilled(value);
return this;
};
function resolve(newValue) {
value = newValue;
state = 'fulfilled';
setTimeout(function () {
callbacks.forEach(function (callback) {
callback(value);
});
}, 0);
}
fn(resolve);
}

上述代码的思路是这样的:resolve执行时,会将状态设置为fulfilled,在此之后调用then添加的新回调,都会立即执行。

这里没有任何地方将state设为rejected,为了让大家聚焦在核心代码上,这个问题后面会有一小节专门加入。

链式Promise

那么这里问题又来了,如果用户再then函数里面注册的仍然是一个Promise,该如何解决?比如下面的例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
27
// 例4
getUserId()
.then(getUserJobById)
.then(function (job) {
// 对job的处理
});
function getUserJobById(id) {
return new Promise(function (resolve) {
http.get(baseUrl + id, function(job) {
resolve(job);
});
});
}

这种场景相信用过promise的人都知道会有很多,那么类似这种就是所谓的链式Promise。

链式Promise是指在当前promise达到fulfilled状态后,即开始进行下一个promise(后邻promise)。那么我们如何衔接当前promise和后邻promise呢?(这是这里的难点)。

其实也不是辣么难,只要在then方法里面return一个promise就好啦。Promises/A+规范中的2.2.7就是这么说哒(微笑脸)~

下面来看看这段暗藏玄机的then方法和resolve方法改造代码:

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
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
function Promise(fn) {
var state = 'pending',
value = null,
callbacks = [];
this.then = function (onFulfilled) {
return new Promise(function (resolve) {
handle({
onFulfilled: onFulfilled || null,
resolve: resolve
});
});
};
function handle(callback) {
if (state === 'pending') {
callbacks.push(callback);
return;
}
//如果then中没有传递任何东西
if(!callback.onResolved) {
callback.resolve(value);
return;
}
var ret = callback.onFulfilled(value);
callback.resolve(ret);
}
function resolve(newValue) {
if (newValue && (typeof newValue === 'object' || typeof newValue === 'function')) {
var then = newValue.then;
if (typeof then === 'function') {
then.call(newValue, resolve);
return;
}
}
state = 'fulfilled';
value = newValue;
setTimeout(function () {
callbacks.forEach(function (callback) {
handle(callback);
});
}, 0);
}
fn(resolve);
}

我们结合例4的代码,分析下上面的代码逻辑,为了方便阅读,我把例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
27
// 例4
getUserId()
.then(getUserJobById)
.then(function (job) {
// 对job的处理
});
function getUserJobById(id) {
return new Promise(function (resolve) {
http.get(baseUrl + id, function(job) {
resolve(job);
});
});
}
  1. then方法中,创建并返回了新的Promise实例,这是串行Promise的基础,并且支持链式调用。
  2. handle方法是promise内部的方法。then方法传入的形参onFulfilled以及创建新Promise实例时传入的resolve均被push到当前promise的callbacks队列中,这是衔接当前promise和后邻promise的关键所在(这里一定要好好的分析下handle的作用)。
  3. getUserId生成的promise(简称getUserId promise)异步操作成功,执行其内部方法resolve,传入的参数正是异步操作的结果id
  4. 调用handle方法处理callbacks队列中的回调:getUserJobById方法,生成新的promise(getUserJobById promise)
  5. 执行之前由getUserId promise的then方法生成的新promise(称为bridge promise)的resolve方法,传入参数为getUserJobById promise。这种情况下,会将该resolve方法传入getUserJobById promise的then方法中,并直接返回。
  6. 在getUserJobById promise异步操作成功时,执行其callbacks中的回调:getUserId bridge promise中的resolve方法
  7. 最后执行getUserId bridge promise的后邻promise的callbacks中的回调。

更直白的可以看下面的图,一图胜千言(都是根据自己的理解画出来的,如有不对欢迎指正):

img

失败处理

在异步操作失败时,标记其状态为rejected,并执行注册的失败回调:

//例5

function getUserId() {

​ return new Promise(function(resolve) {

​ //异步请求

​ http.get(url, function(error, results) {

​ if (error) {

​ reject(error);

​ }

​ resolve(results.id)

​ })

​ })

}

getUserId().then(function(id) {

​ //一些处理

}, function(error) {

​ console.log(error)

})

有了之前处理fulfilled状态的经验,支持错误处理变得很容易,只需要在注册回调、处理状态变更上都要加入新的逻辑:

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
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
function Promise(fn) {
var state = 'pending',
value = null,
callbacks = [];
this.then = function (onFulfilled, onRejected) {
return new Promise(function (resolve, reject) {
handle({
onFulfilled: onFulfilled || null,
onRejected: onRejected || null,
resolve: resolve,
reject: reject
});
});
};
function handle(callback) {
if (state === 'pending') {
callbacks.push(callback);
return;
}
var cb = state === 'fulfilled' ? callback.onFulfilled : callback.onRejected,
ret;
if (cb === null) {
cb = state === 'fulfilled' ? callback.resolve : callback.reject;
cb(value);
return;
}
ret = cb(value);
callback.resolve(ret);
}
function resolve(newValue) {
if (newValue && (typeof newValue === 'object' || typeof newValue === 'function')) {
var then = newValue.then;
if (typeof then === 'function') {
then.call(newValue, resolve, reject);
return;
}
}
state = 'fulfilled';
value = newValue;
execute();
}
function reject(reason) {
state = 'rejected';
value = reason;
execute();
}
function execute() {
setTimeout(function () {
callbacks.forEach(function (callback) {
handle(callback);
});
}, 0);
}
fn(resolve, reject);
}

上述代码增加了新的reject方法,供异步操作失败时调用,同时抽出了resolve和reject共用的部分,形成execute方法。

错误冒泡是上述代码已经支持,且非常实用的一个特性。在handle中发现没有指定异步操作失败的回调时,会直接将bridge promise(then函数返回的promise,后同)设为rejected状态,如此达成执行后续失败回调的效果。这有利于简化串行Promise的失败处理成本,因为一组异步操作往往会对应一个实际功能,失败处理方法通常是一致的:

//例6

getUserId()

​ .then(getUserJobById)

​ .then(function (job) {

​ // 处理job

​ }, function (error) {

​ // getUserId或者getUerJobById时出现的错误

​ console.log(error);

​ });

异常处理

细心的同学会想到:如果在执行成功回调、失败回调时代码出错怎么办?对于这类异常,可以使用try-catch捕获错误,并将bridge promise设为rejected状态。handle方法改造如下:

function handle(callback) {

​ if (state === ‘pending’) {

​ callbacks.push(callback);

​ return;

​ }

​ var cb = state === ‘fulfilled’ ? callback.onFulfilled : callback.onRejected,

​ ret;

​ if (cb === null) {

​ cb = state === ‘fulfilled’ ? callback.resolve : callback.reject;

​ cb(value);

​ return;

​ }

​ try {

​ ret = cb(value);

​ callback.resolve(ret);

​ } catch (e) {

​ callback.reject(e);

​ }

}

如果在异步操作中,多次执行resolve或者reject会重复处理后续回调,可以通过内置一个标志位解决。

总结

刚开始看promise源码的时候总不能很好的理解then和resolve函数的运行机理,但是如果你静下心来,反过来根据执行promise时的逻辑来推演,就不难理解了。这里一定要注意的点是:promise里面的then函数仅仅是注册了后续需要执行的代码,真正的执行是在resolve方法里面执行的,理清了这层,再来分析源码会省力的多。

现在回顾下Promise的实现过程,其主要使用了设计模式中的观察者模式:

  1. 通过Promise.prototype.then和Promise.prototype.catch方法将观察者方法注册到被观察者Promise对象中,同时返回一个新的Promise对象,以便可以链式调用。
  2. 被观察者管理内部pending、fulfilled和rejected的状态转变,同时通过构造函数中传递的resolve和reject方法以主动触发状态转变和通知观察者。

ES6 Iterator

发表于 2017-06-08 | 分类于 ES6

ES6 Set Map

发表于 2017-06-08 | 分类于 ES6

最近一直在忙react项目,好久没写博客了,需要用到immutable数据,这里先预热下map和set数据

1 Set数据结构,是ES6的新的数据结构,该数据结构类似于数组,但是和数组不同的一点就是,数组中可以有相同的成员,但是Set数据结构不允许数据成员有一样的,Set数据结构的成员都是唯一的.

有序且不可重复的列表

1
Set(4) {"car", "buble", "dog", "name"}

Set是一个构造函数,可以用来生成set数据结构,生成的

1
2
3
var set1 = new Set()
console.log(set);
var set2 = new Set([1,2,3,4,4]) //这里的4最终在Set数据结构中只存在一项

Set构造函数可以直接接受一个数组作为参数,然后将该数组初始化为Set数据结构,但是不能接受一个对象作为参数,会报错

1
2
3
var obj = {name:'Jhon',age:'13'}
var set = new Set(obj)
console.log(set) //报错

1.1 Set数据结构的属性和方法,通过输出我们在控制台可以看到Set构造函数原型链上的所有的API

1
2
3
4
5
-size 输出Set数据结构的成员个数
-add(value) 向Set数据实例添加成员,返回Set数据结构,可以进行链式操作
-clear() 删除Set数据结构中所有的成员,没有返回值
-delete(value) 删除Set数据结构中某一个成员,返回布尔值,表示是否删除成功
-has(value) 判断Set数据结构中是否包括某个成员,返回布尔值,表示是否包括某个成员

对于我们常用的数组去重,这里就有了更好的实现方法

这里需要明确Array.from() 方法的使用,该方法接受

  • 类数组对象:拥有一个length属性和若干索引属性的任意对象
  • 可遍历对象:比如Map 和Set 数据结构
1
2
3
数组去重的更加简单的方式
var s = new Set([1,2,3,5,5])
console.log(Array.from(s))

1.2 Set数据结构的遍历方法(Set数据结构可以理解为键名和键值是相同的)

1
2
3
4
-keys 返回遍历器对象(由于Set数据结构没有键名,只有键值,或者说键名和键值一样)所以keys和values方法行为完全一致
-values (这个是Set数据结构默认遍历器接口,for-of循环)
-entries 返回遍历器对象
-forEach 没有返回值,对每个成员执行操作
1
2
3
4
5
6
7
8
9
10
11
12
13
14
var set = new Set(['car','buble','dog'])
set.add('name')
console.log(set)
for(let i of set){ //Set结构实例默认可遍历,默认遍历器生成函数是values方法
console.log(i); //car bubble dog name
}
console.log('setForeach=====')
set.forEach((value,key)=>console.log(value,key))
/*
car car
bubble bubble
dog dog
name name
**/

2 Map数据结构,是ES6的新的数据结构,该数据结构类似于对象,也是键值对的集合,但是和对象不同的一点就是,对象中的中键名只能是字符串,但是Map数据结构的键名可以是任何数据类型,包括对象和数组

键值对集合,对应于Object,Map对象

1
Map(2) {"a" => 1, "b" => 2}

所以这里需要注意的是Map数据结构的键名如果是复杂数据类型,那么判断键名是否一致的根本比较方式是判断的键名的内存地址,也就是说{ } 和 { } 这两个键名代表的是不同的键名.

1
2
3
4
5
var obj = {name:'Jhon',age:'13'}
var map = new Map()
console.log(map)
map.set(obj,'hello Map') //对象作为键名
console.log(map.get(obj))

Map是一个构造函数,用来生成一个Map结构的数据对象

1
2
var map = new Map()
console.log(map)

1.1 Map数据结构的属性和方法,通过输出我们在控制台可以看到Set构造函数原型链上的所有的API

1
2
3
4
5
-set(key,value) 给Map数据结构设置键值对,返回Map实例,所以可以进行链式调用,如果包含该键名,则会更新
-get(key) 获取Map数据结构键名对应的键值,如果找不到则返回undefined
-has(key) 判断Map数据结构是否包含某个键名,返回布尔类型值
-delete(key) 删除Map数据结构某个key,如果成功删除则返回ture,删除失败返回false
-clear() 方法清除所有成员,没有返回值

1.2 Map数据结构的遍历

1
2
-keys() -values() 返回键名的遍历器 和键值的遍历器
-entries() 返回键名和键值组成的遍历器(Map数据默认遍历器接口就是这个 for-of )
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
var obj = {name:'Jhon',age:'13'}
var arr = [1,2,3 ]
var map = new Map()
console.log(map)
map.set(obj,'hello Map')
map.set('heh',"hahahahh")
console.log(map.set(obj,'hello Map'))
console.log(map.get(obj))
console.log(map.has(obj))
console.log(map.get(obj))
for(let key of map.keys()){
console.log(key)
}
for(let key of map.values()){
console.log(key)
}
for(let key of map.entries()){
console.log(key)
}

1.3 Map构造函数接受的参数:原生数据结构具有Iterator接口,比如数组,类数组对象,Map和Set数据

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
const map = new Map([
['F', 'no'],
['T', 'yes'],
]);
for (let key of map.keys()) {
console.log(key);
}
// "F"
// "T"
for (let value of map.values()) {
console.log(value);
}
// "no"
// "yes"
for (let item of map.entries()) {
console.log(item[0], item[1]);
}
// "F" "no"
// "T" "yes"
// 或者
for (let [key, value] of map.entries()) {
console.log(key, value);
}
// "F" "no"
// "T" "yes"
// 等同于使用map.entries()
for (let [key, value] of map) {
console.log(key, value);
}
// "F" "no"
// "T" "yes"

1.4 Map数据结构转化为数组,通过使用 ... 扩展运算符,数组转化为Map结构,可以作为Map构造函数参数直接传入

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
const map = new Map([
[1, 'one'],
[2, 'two'],
[3, 'three'],
]);
[...map.keys()]
// [1, 2, 3]
[...map.values()]
// ['one', 'two', 'three']
[...map.entries()]
// [[1,'one'], [2, 'two'], [3, 'three']]
[...map]
// [[1,'one'], [2, 'two'], [3, 'three']]

Redux thunk源码

发表于 2017-05-24 | 分类于 redux

1 redux-thunk源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
'use strict';
exports.__esModule = true;
function createThunkMiddleware(extraArgument) {
return function (_ref) {
var dispatch = _ref.dispatch,
getState = _ref.getState;
return function (next) {
return function (action) {
if (typeof action === 'function') {
return action(dispatch, getState, extraArgument);
}
return next(action);
};
};
};
}
var thunk = createThunkMiddleware();
thunk.withExtraArgument = createThunkMiddleware;
exports['default'] = thunk;

什么是thunk

1
2
3
// Meet thunks.
// A thunk is a function that returns a function.
// This is a thunk.

redux-thunk

未命名

发表于 2017-05-23

ES6-Promise

发表于 2017-05-20 | 分类于 ES6

1 Promise定义

Promise是一种异步编程的解决方案,是一个对象(构造函数),从该对象可以获取异步操作的消息

Promise对象的特点

(1)对象的状态不受外界影响。Promise对象代表一个异步操作,有三种状态:Pending(进行中)、Resolved(已完成,又称 Fulfilled)和Rejected(已失败)。只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态。这也是Promise这个名字的由来,它的英语意思就是“承诺”,表示其他手段无法改变。

(2)一旦状态改变,就不会再变,任何时候都可以得到这个结果。Promise对象的状态改变,只有两种可能:从Pending变为Resolved和从Pending变为Rejected。只要这两种情况发生,状态就凝固了,不会再变了,会一直保持这个结果。如果改变已经发生了,你再对Promise对象添加回调函数,也会立即得到这个结果。这与事件(Event)完全不同,事件的特点是,如果你错过了它,再去监听,是得不到结果的。

缺点

(1) 首先,无法取消Promise,一旦新建它就会立即执行,无法中途取消。

(2) 当新建promise对象的时候,Promise里面必须传入回调函数,否则内部会抛出错误

1
var promise = new Promise() //报错

(3) 当处于Pendding状态的时候,无法确切知道处于哪个阶段的状态

我们可以打印出来看下有什么

1
2
3
4
5
6
7
8
9
console.dir(Promise);
//我们可以看到起原型上有 then 和catch等方法
// var promise = new Promise(); Promise是一个构造函数,构造函数中必须传入一个函数作为参数,否则会报错
var promise = new Promise(function(resolve,reject){
console.log(arguments);//
// resolve();
reject();
})
console.log(promise);
1
2
3
4
//promise实例上有如下属性
Promise
[[PromiseStatus]]:"resolved"
[[PromiseValue]]:undefined

2 基本使用 Promise是一个构造函数,该构造函数接受一个函数作为参数(必须的)

写在前面,promise对象创建的时候立即执行>同步>异步>回调函数

接受的函数中又有两个函数作为参数,

  • Promise构造函数中必须有一个函数作为构造函数的参数,作为参数的函数的参数有两个函数
  • 一个是resolve函数,在异步操作成功的时候执行该函数,将Pendding状态改变为Resolved
  • 一个是reject函数,在异步操作失败的时候执行该函数,将Pendding状态改变为Rejected
  • 创建promise对象之后,根据异步操作成功与否,调用resolve或者reject函数,改变状态的结果,then方法会根据改变的状态结果调用响应的回调函数,then方法接受两个函数,
    • 第一个函数在Resolved状态的时候执行
    • 第二个函数在Rejected状态的时候执行

基本的实现思路是

1
2
3
4
5
6
7
8
9
var promise = new Promise(function(resolve, reject) {
// ... some code
if (/* 异步操作成功 */){
resolve(value);
} else {
reject(error);
}
});
1
2
3
4
5
promise.then(function(value) {
// success
}, function(error) {
// failure
});

如下demo所示

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var promise = new Promise(function(resolve,reject){
console.log('promise对象创建之后立即执行');
// resolve();
// rejecte();
})
console.log('helloWorld');
promise.then(function(){
console.log('this is the status of Resolved');
},function(){
console.log('this is the status of Rejected');
})
  • 当打开resolve()的注释的时候,改变了promise实例的状态,触发then函数中的第一个回调函数的执行
1
2
3
promise对象创建之后立即执行
helloWorld
this is the status of Resolved
  • 当打开reject()的注释的时候,改变了promise实例的状态,触发then函数中的第二个回调函数的执行
1
2
3
promise对象创建之后立即执行
helloWorld
this is the status of Rejected

3 实例化一个Promise对象的时候传入Promise构造函数的参数是一个函数,该函数立即执行

内部实现大概是这个样子的,我猜 ; 所以才会有上面的输处顺序

1
2
3
4
5
6
7
8
<script>
function foo(f1){
f1()
}
new foo(function(){
console.log('f1 is exected');
});
</script>

4 如果 resolve 或者 reject 函数执行的时候有参数,那么参数会传递给then方法中相应的回调函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<script>
var promise = new Promise((resolve,reject)=>{
var res = 'RES';
var rej = 'REJ';
resolve(res);
reject(rej);
})
promise.then(value => {
console.log('这是通过resolve函数传递过来的参数',value);
},value => {console.log('这是通过reject函数传递过来的参数',value);
})
</script>

另外,一般而言reject函数的参数一般是一个ERROR对象的实例,resolve函数的参数也可能是另外一个Promise对象的实例

先看下面一段代码,

  • 如果promise对象不是另外一个promise对象的resolve函数的参数,那么promise实例 p1 p2的状态互不影响
1
2
3
4
5
6
7
8
9
10
11
12
13
<script>
var p1 = new Promise(function(resolve,reject){
})
var p2 = new Promise(function(resolve,reject){
resolve() ;
})
console.log('p1',p1); //p1 Pendding
console.log('p2',p2);//p2 Resolved
</script>
  • 但是如果promise实例是另外一个promise对象的resolve的参数的话,那么promise实例将会和resolve参数的promise对象的状态保持一致
1
2
3
4
5
6
7
8
9
10
11
12
13
<script>
var p1 = new Promise(function(resolve,reject){
//此时p1对象的状态是Pendding
})
var p2 = new Promise(function(resolve,reject){
resolve(p1) ; //将p1对象作为resolve函数的参数
})
console.log('p1',p1);//p1 Pendding
console.log('p2',p2);//p2 Pendding
</script>

改变p1对象的状态

1
2
3
4
5
6
7
8
9
10
11
12
13
<script>
var p1 = new Promise(function(resolve,reject){
resolve();//此时p1对象的状态是Resolved
})
var p2 = new Promise(function(resolve,reject){
resolve(p1) ;
})
console.log('p1',p1);//p1 Resolved
console.log('p2',p2);//p2 Resolved
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
<script>
var p1 = new Promise(function(resolve,reject){
// resolve();
reject();
})
var p2 = new Promise(function(resolve,reject){
resolve(p1) ;
})
console.log('p1',p1);
console.log('p2',p2);
</script>

5 Promise.prototype.then Promise.prototype.catch

  • then方法是定义在Prototype构造函数原型上的一个方法
  • 该方法接受两个函数作为参数
    • 第一个参数函数在promise实例状态变为Resolved的时候会执行
    • 第二个参数函数在promise实例状态变为Rejected的时候会执行
  • 该方法返回值是 另外一个promise对象
  • catch方法:当一个Promise实例的状态变为Rejected的时候会调用catch方法里面的回调函数
1
2
3
4
5
6
7
8
9
10
11
12
<script>
var p = new Promise(function(resolve,reject){
resolve()
})
var ret = p.then(function(){
console.log('this is the status of Resolved');
})
console.log(ret);//Promise对象
</script>
1
2
3
4
5
6
7
8
9
<script>
var p = new Promise((resolve,reject)=>{
reject();
})
p.catch(function(){
console.log('REJECTED');
})
</script>

基本使用暂时到此,后期其他方法会有时间更新 =========== 2017/5/11 21:21

then链式调用的时候,then函数的返回值,会作为第下一个then函数中第一个参数函数的参数

1
2
3
4
5
6
7
8
9
10
11
12
13
var p = new Promise(function(resolve,reject){
resolve()
})
var ret = p.then(function(){
console.log('this is the status of Resolved');
return 2 // false 或者不返回值,此时val的值将是undefined
},function(){
console.log('resolve')
}).then(function(val){
console.log('inner Resolved');
console.log(val) //2 false undefined
})
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
var test ={
func1:function(){
var data = new Promise(function(resolve){
setTimeout(function(){
resolve("ajax结果111")
},200)
})
return data;
},
func2:function(){
var data = new Promise(function(resolve){
setTimeout(function(){
resolve("ajax结果222")
},100)
})
return data;
},
func3:function(){
var data = new Promise(function(resolve){
setTimeout(function(){
resolve("ajax结果333")
},500)
})
return data;
}
}
//resolve函数的参数将会作为then函数中第一个函数的参数
test.func1().then(function(value){
console.log(value)
//do something...
})
//then的链式调用前奏
test.func1().then(function(value){
console.log(value) //ajax结果111
test.func2().then(function(value){
console.log(value) //ajax结果2
test.func3().then(function(value){
console.log(value) //ajax结果333
//do something...
})
})
})

使用return可以更加方便的进行链式调用

1
2
3
4
5
6
7
8
9
10
test.func1().then(function(value){
console.log(value)
//do something...
return test.func2();
}).then(function(value){
console.log(value)
return test.func3();
}).then(function(value){
console.log(value)
})

===2017/6/14更新

6 fetch方法

6.1 目前在做react项目,用到了fetch发送异步请求,此时的fetch其实就是封装了一分Promise对象。

先来看下如何封装Ajax

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
var getJSON = function(url) {
var promise = new Promise(function(resolve, reject){
var client = new XMLHttpRequest();
client.open("GET", url);
client.onreadystatechange = handler;
client.responseType = "json";
client.setRequestHeader("Accept", "application/json");
client.send();
function handler() {
if (this.readyState !== 4) {
return;
}
if (this.status === 200) {
resolve(this.response);
} else {
reject(new Error(this.statusText));
}
};
});
return promise;
};
getJSON("/posts.json").then(function(json) {
console.log('Contents: ' + json);
}, function(error) {
console.error('出错了', error);
});
1
2
3
4
5
6
7
fetch(url).then(function(response) { //then方法返回一个新的Promise对象,
return response.json();
}).then(function(data) { //这个data就是通过
console.log(data);
}).catch(function(e) {
console.log("Oops, error");
});

React-router Switch Redict

发表于 2017-05-18 | 分类于 react

1 Redict重定向组件

1
<Redirect from="/old-match" to="/will-match"/>

2 Switch组件

用来匹配子路由中第一个匹配到的path路由组件,匹配到即停止往下进行路径的匹配

对比一个例子

官网原demo代码

1
2
3
4
5
6
<Switch>
<Route path="/" exact component={Home}/>
<Redirect from="/old-match" to="/will-match"/>
<Route path="/will-match" component={WillMatch}/>
<Route component={NoMatch}/>
</Switch>
1
2
3
4
5
6
7
8
<Route>
<div>
<Route path="/" exact component={Home}/>
<Redirect from="/old-match" to="/will-match"/>
<Route path="/will-match" component={WillMatch}/>
<Route component={NoMatch}/>
</div>
</Route>

3 Router组件 Route组件只能有一个根子元素,而Switch组件可以有多个并列的多子组件,

  • Route组件作为Router的子组件时候,如果Route的path属性如果没有,那么该组件始终会匹配
  • Route组件作为作为Switch组件的时候,如果前面有匹配到的路径,那么后面的Route的路径就不会再去匹配
  • 可以总结来说Switch组件会按顺序进行子组件的path进行判断,匹配到了即进行渲染,终止后续Route组件的路径匹配 ;Router组件会按顺序进行子组件的path判断,会将所有的子Route的path路径进行匹配.
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
import React from 'react'
import {
BrowserRouter as Router,
Route,
Link,
Switch,
Redirect
} from 'react-router-dom'
const NoMatchExample = () => (
<Router>
<div>
<ul>
<li><Link to="/">Home</Link></li>
<li><Link to="/old-match">Old Match, to be redirected</Link></li>
<li><Link to="/will-match">Will Match</Link></li>
<li><Link to="/will-not-match">Will Not Match</Link></li>
<li><Link to="/also/will/not/match">Also Will Not Match</Link></li>
</ul>
<Switch>
<Route path="/" exact component={Home}/>
<Redirect from="/old-match" to="/will-match"/>
<Route path="/will-match" component={WillMatch}/>
<Route component={NoMatch}/>
</Switch>
</div>
</Router>
)
const Home = () => (
<p>
A <code>&lt;Switch></code> renders the
first child <code>&lt;Route></code> that
matches. A <code>&lt;Route></code> with
no <code>path</code> always matches.
</p>
)
const WillMatch = () => <h3>Matched!</h3>
const NoMatch = ({ location }) => (
<div>
<h3>No match for <code>{location.pathname}</code></h3>
</div>
)
export default NoMatchExample

React-router Prompt

发表于 2017-05-17 | 分类于 react

1 看下官方解释

弹窗出来之后点击确定返回true,点击取消返回false.

Used to prompt the user before navigating away from a page. When your application enters a state that should prevent the user from navigating away (like a form is half-filled out), render a <Prompt>.

Prompt组件,当我们想往其他url地址跳转的时候,如果when的属性值是true,则会执行message

1
2
3
4
5
6
import { Prompt } from 'react-router'
<Prompt
when={formIsHalfFilledOut}
message="Are you sure you want to leave?"
/>

when: bool

Instead of conditionally rendering a <Prompt> behind a guard, you can always render it but pass when={true} or when={false} to prevent or allow navigation accordingly.

粗暴点来理解,

  • 如果我们传入一个true,那么则会渲染Prompt,接着执行message函数,渲染该函数的返回值,会询问用户是否确定跳转.
  • 如果传入一个false,那么就会直接跳转到我们点击的url链接,

message: string

The message to prompt the user with when they try to navigate away.

1
<Prompt message="Are you sure you want to leave?"/>

message: func

Will be called with the next location and action the user is attempting to navigate to.

  • Return a string to show a prompt to the user 此时如果用户点击确定,则返回true,允许跳转
  • or true to allow the transition.
  • 传入message函数的变量是下一个location对象
1
2
3
<Prompt message={location => (
`Are you sure you want to go to ${location.pathname}?`
)}/>
1
2
3
4
5
6
<Prompt
when={isBlocking}
message={location => (
true
)}
/>

2 官方demo改动理解

1
2
3
4
5
6
7
8
<Prompt
when={false}
message={location => {
console.log({isBlocking})
return `Are you sure you want to go to ${location.pathname}`
}}
/>
1
2
3
4
5
6
7
8
<Prompt
when={true}
message={location => {
console.log({isBlocking})
return `Are you sure you want to go to ${location.pathname}`
}}
/>
1
2
3
4
<Prompt
when={true}
message='are you sure ? dearling'
/>

3 官方demo如下

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
66
67
68
69
70
71
72
73
74
75
import React from 'react'
import {
BrowserRouter as Router,
Route,
Link,
Prompt
} from 'react-router-dom'
const PreventingTransitionsExample = () => (
<Router>
<div>
<ul>
<li><Link to="/">Form</Link></li>
<li><Link to="/one">One</Link></li>
<li><Link to="/two">Two</Link></li>
</ul>
<Route path="/" exact component={Form}/>
<Route path="/one" render={() => <h3>One</h3>}/>
<Route path="/two" render={() => <h3>Two</h3>}/>
</div>
</Router>
)
class Form extends React.Component {
state = {
isBlocking: false
}
render() {
const { isBlocking } = this.state //对象的结构赋值
//等价于 const isBlocking = this.state.isBlocking
return (
<form
onSubmit={event => {
console.log('button is pressed');
event.preventDefault()
event.target.reset()
this.setState({
isBlocking: false
})
}}
>
<Prompt
when={isBlocking}
message={location => (
`Are you sure you want to go to ${location.pathname}`
)}
/>
<p>
Blocking? {isBlocking ? 'Yes, click a link or the back button' : 'Nope'}
</p>
<p>
<input
size="50"
placeholder="type something to block transitions"
onChange={event => {
this.setState({
isBlocking: event.target.value.length > 0
})
}}
/>
</p>
<p>
<button>Submit to stop blocking</button>
</p>
</form>
)
}
}
export default PreventingTransitionsExample

React-router 中的Route组件详解

发表于 2017-05-16 | 分类于 react

1 Route组件

The Route component is perhaps the most important component in React Router to understand and learn to use well. Its most basic responsibility is to render some UI when a location matches the route’s path.

Consider the following code:

1
2
3
4
5
6
7
8
import { BrowserRouter as Router, Route } from 'react-router-dom'
<Router>
<div>
<Route exact path="/" component={Home}/>
<Route path="/news" component={NewsFeed}/>
</div>
</Router>

If the location of the app is / then the UI hierarchy will be something like:

1
2
3
4
<div>
<Home/>
<!-- react-empty: 2 -->
</div>

And if the location of the app is /news then the UI hierarchy will be:

1
2
3
4
<div>
<!-- react-empty: 1 -->
<NewsFeed/>
</div>

The “react-empty” comments are just implementation details of React’s null rendering. But for our purposes, it is instructive. A Route is always technically “rendered” even though its rendering null. As soon as the app location matches the route’s path, your component will be rendered.

####Route render methods

There are 3 ways to render something with a <Route>:

  • <Route component />
  • <Route render />
  • <Route children />

Each is useful in different circumstances. You should use only one of these props on a given <Route>. See their explanations below to understand why you have 3 options. Most of the time you’ll use component.

Route props

All three render methods will be passed the same three route props

  • match
  • location
  • history

component

A React component to render only when the location matches. It will be rendered with route props.

1
2
3
4
5
<Route path="/user/:username" component={User}/>
const User = ({ match }) => {
return <h1>Hello {match.params.username}!</h1>
}

When you use component (instead of render or children, below) the router uses React.createElement to create a new React element from the given component. That means if you provide an inline function to the component attribute, you would create a new component every render. This results in the existing component unmounting and the new component mounting instead of just updating the existing component. When using an inline function for inline rendering, use the render or the children prop (below).

render: func

This allows for convenient inline rendering and wrapping without the undesired remounting explained above.

Instead of having a new React element created for you using the component prop, you can pass in a function to be called when the location matches. The render prop receives all the same route props as the component render prop.

当路径匹配到了的时候,就会执行render函数

1
2
3
4
5
6
7
8
9
10
11
12
13
// convenient inline rendering
<Route path="/home" render={() => <div>Home</div>}/>
// wrapping/composing
const FadingRoute = ({ component: Component, ...rest }) => (
<Route {...rest} render={props => (
<FadeIn>
<Component {...props}/>
</FadeIn>
)}/>
)
<FadingRoute path="/cool" component={Something}/>

Warning: <Route component> takes precendence over <Route render> so don’t use both in the same <Route>.

children: func

Sometimes you need to render whether the path matches the location or not. In these cases, you can use the function children prop. It works exactly like render except that it gets called whether there is a match or not.

无论path是否匹配到了路径,都会渲染children

The children render prop receives all the same route props as the component and render methods, except when a route fails to match the URL, then match is null. This allows you to dynamically adjust your UI based on whether or not the route matches. Here we’re adding an active class if the route matches

1
2
3
4
5
6
7
8
9
10
11
12
<ul>
<ListItemLink to="/somewhere"/>
<ListItemLink to="/somewhere-else"/>
</ul>
const ListItemLink = ({ to, ...rest }) => (
<Route path={to} children={({ match }) => (
<li className={match ? 'active' : ''}>
<Link to={to} {...rest}/>
</li>
)}/>
)

This could also be useful for animations:

1
2
3
4
5
6
7
<Route children={({ match, ...rest }) => (
{/* Animate will always render, so you can use lifecycles
to animate its child in and out */}
<Animate>
{match && <Something {...rest}/>}
</Animate>
)}/>

Warning: Both <Route component> and <Route render> take precendence over <Route children> so don’t use more than one in the same <Route>.

path: string

Any valid URL path that path-to-regexp understands.

1
<Route path="/users/:id" component={User}/>

Routes without a path always match.

如果一个Route组件没有path属性,那么这个组件将总会被渲染.

Route组件的path路径如果包含 : 变量,那么匹配到的所有的符合变量路径都会被渲染,如果想要保证唯一性,可以通过使用Switch组件进行匹配到一个则不进行往下匹配选择性渲染.

exact: bool

When true, will only match if the path matches the location.pathname exactly.

1
<Route exact path="/one" component={About}/>
path location.pathname exact matches?
/one /one/two true no
/one /one/two false yes

strict: bool

When true, a path that has a trailing slash will only match a location.pathname with a trailing slash. This has no effect when there are additional URL segments in the location.pathname.

1
<Route strict path="/one/" component={About}/>
path location.pathname matches?
/one/ /one no
/one/ /one/ yes
/one/ /one/two yes

Warning: strict can be used to enforce that a location.pathname has no trailing slash, but in order to do this both strictand exact must be true.

1
<Route exact strict path="/one" component={About}/>
path location.pathname matches?
/one /one yes
/one /one/ no
/one /one/two no

2 Demo案例

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
import React from 'react'
import {
BrowserRouter as Router,
Route,
Link
} from 'react-router-dom'
const Repo = ()=>(<div>this is Repo</div>)
const Category = (props)=>{
console.log(props);
return (<div>this is category</div>)
}
const MyTest =()=>(
<Router>
<div>
<ul>
<li>
<Link to='/about'>About</Link>
</li>
<li>
<Link to='./repo'>Repo</Link>
</li>
<li>
<Link to='./category'>Category</Link>
</li>
</ul>
<Route exact path='/about' render={(props)=>{console.log(props);return (<div>this is aabout</div>)
}}></Route>
<Route exact path='/repo' component={Repo}> </Route>
<Route exact path='/category' component={Category}> </Route>
<Route children={(props)=>{console.log(props);return (<div>this is a component build througth children</div>)
}}></Route>
</div>
</Router>
)
export default MyTest

Recursive递归 : match对象的使用,path就是Route组件的path值,params对象存放的是 :id 的key/value对应的值,value值是字符串,url是当前路径的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
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
import React from 'react'
import {
BrowserRouter as Router,
Route,
Link
} from 'react-router-dom'
//这个时候的UI视图就是根据数据来呈现的
const PEEPS = [
{ id: 0, name: 'Michelle', friends: [ 1, 2, 3 ] },
{ id: 1, name: 'Sean', friends: [ 0, 3 ] },
{ id: 2, name: 'Kim', friends: [ 0, 1, 3 ], },
{ id: 3, name: 'David', friends: [ 1, 2 ] }
]
const find = (id) => PEEPS.find( p=>p.id == id )
const RecursiveExample = ()=>(
<Router>
<Person match={{params:{id:0},url:''}}></Person>
</Router>
)
const Person = ({match})=>{
console.log('match',match);
const person = find(match.params.id)
console.log(person);
return (
<div>
<h3>{person.name}'s friends</h3>
<ul>
{person.friends.map(id=>(
<li key={id}>
<Link to={`${match.url}/${id}`}>
{find(id).name}
</Link>
</li>
))}
</ul>
<Route path={`${match.url}/:id`} component={Person}></Route>
</div>
)
}
export default RecursiveExample

3 Route组件会将history location match这三个对象传递给component render 以及children中的props

  • <Route component />
  • <Route render />
  • <Route children />

这三种方式创建的组件的props属性中.

4 Routee组件作为路由根组件需要注意两点

  • Router组件只允许有一个子元素,不允许有并行的子元素
  • Router组件可以通过history对象进行对url地址的记录.
12…20
JiM-W

JiM-W

keep fighting!

195 日志
52 分类
90 标签
© 2017 JiM-W
由 Hexo 强力驱动
主题 - NexT.Pisces