个人对于fetch的疑问地方:
-
fetch为什么可以使用then?(个人理解then方法是定义在原型对象Promise.prototype上的)
-
为什么使用两次then才能取出数据?
fetch 为什么可以使用 then?
要想了解这个,首先要先理解Promise
的用法和原理,推荐阮一峰 的ECMAScript 6 入门:Promise 对象,里面介绍到promise
和then
的具体用法。
Promise对象代表一个异步操作,有三种状态:pending
(进行中)、fulfilled
(已成功)和rejected
(已失败)。只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态。这也是Promise这个名字的由来,它的英语意思就是“承诺”,表示其他手段无法改变。
const promise = new Promise(function(resolve, reject) {
// ... some code
if (/* 异步操作成功 */){
resolve(value);
} else {
//异步操作失败
reject(error);
}
});
promise.then(function(value) {
// success
}, function(error) {
// failure
});
Promise 实例具有 then 方法,也就是说,then 方法是定义在原型对象Promise.prototype
上的。它的作用是为 Promise 实例添加状态改变时的回调函数,当异步操作成功,执行 resolve 方法,改变 promise 状态为 resolved,使 then 方法执行第一个回调函数,第一个函数参数为 resolve 传递出来的数据。then 方法的第一个参数是 resolved 状态的回调函数,第二个参数(可选)是 rejected 状态的回调函数。(这里涉及到js的事件循环机制,想要了解点这里传送门)
then 方法返回的是一个新的 Promise 实例(注意,不是原来那个 promise实例)。因此可以采用链式写法,即 then 方法后面再调用另一个 then 方法,这句话跟 fetch 的用法是一样的,由于 then 的返回值是一个 promise 实例,可以采用的是链式写法,所以,fetch() 其实就是封装了的 promise 的函数,返回值是 promise 的实例,所以才能调用 then 用法,所以说,fetch()其实就是 promise 的实例。
那么第二个疑问呢?
为什么使用两次 then 才能取出数据?
fetch(url).then(function(response) {
return response.json();
}).then(function(data) {
console.log(data);
}).catch(function(err) {
console.log("error");
});
fetch() 必须接受一个参数——资源的路径url
。无论请求成功与否,它都返回一个 Promise 对象,resolve 返回对应请求的 Response
。一旦 Response 被返回,就可以使用一些方法来定义内容的形式,以及应当如何处理内容。
上面这段话代表 fetch 的第一个 then 执行的第一个 resolve 回调函数,函数参数为 response
,怎么处理返回的对象呢?
response是Response
对象,包含Header
、status
、statusText
等属性。要获得具体数据需要使用.json(用于JSON
)、.text(用于文本)、.formData(用于FormData
对象)等方法。至于为什么需要return,因为Response.json
返回的是一个Promise,所以只能先return,再在下一层处理。
直接输出 Response 对象不是我们需要的数据,使用response.json()或者response.text()等方法获取到我们需要的数据。
其他数据转换方法:
- Body.arrayBuffer()
使Response挂起一个流操作并且在完成时读取其值,它返回一个Promise对象,其resolve参数类型是ArrayBuffer。 - Body.blob()
使Response挂起一个流操作并且在完成时读取其值,它返回一个Promise对象,其resolve参数类型是Blob。 - Body.formData()
使Response挂起一个流操作并且在完成时读取其值,它返回一个Promise对象,其resolve参数类型是FormData表单。 - Body.json()
使Response挂起一个流操作并且在完成时读取其值,它返回一个Promise对象,其resolve参数类型是使用JSON解析body文本的结果。 - Body.text()
使Response挂起一个流操作并且在完成时读取其值,它返回一个Promise对象,其resolve参数类型是USVString(文本)。
以response.json()为例,它返回一个 Promise,Promise 的解析 resolve 结果是将文本体解析为 JSON,数据在[[[PromiseValue]]里面,但是直接取是取不出来的。没有方法取出来,Promise的设计文档中说了,[[PromiseValue]]是个内部变量,外部无法得到,只能在then中获取,可以调用then方法去处理数据。所以就会用到第二次then了。
现在就重点理解下[[[PromiseValue]]这个怎么获取到的?
代码中的 resolve() 就是说明 resolve 内部是怎么运行的,改变 promise 的状态,给PromiseValue复制:
/* 用于描述思维的代码 */
executor(resolve, reject) {
// ... some code
resolve(value);
...
}
...
resolve(value) {
PromiseStatus = 'fulfilled';
PromiseValue = value;
...
// 接着调用回调链中的回调函数
// ... some code
}
resolve(value) ,参数 value 的实参是 PromiseValue。这句话是说 then 的回调函数参数使用的都是 PromiseValue,所以直接输出就会获取到 PromiseValue 的值。
这里有一点值得注意:第一个 then 的 return 返回值是一个 promise 实例对象,那么由 then() 构造的回调链会转交给新的 Promise 对象并完成调用,第二个 then 的回调函数参数为 PromiseValue 的值。但当 return 返回值不是 promise 对象时,会将该 return 返回值赋值给 PromiseValue,供下次的 then 函数做为实参使用。
const promise = new Promise(function(resolve, reject) {
console.log('Promise');
resolve('第一次resolve');
});
promise.then(function(value) {
console.log(value);
//当 return 返回值不是 promise 对象时
//会将该 return 返回值赋值提供下次的 then 函数做为实参使用
return '第二次resolve'
}).then(function(value) {
console.log(value);
})
// 打印结果
// Promise
// 第一次resolve
// 第二次resolve
回调链是啥??
then(resolve, reject):这个方法实际上是把 resolve() 函数和 reject() 函数添加到 Promise 对象的回调链中。
回调链就像一个由函数组构成的队列,每一组函数都是由至少一个函数构成(resolve() 和 reject()(可选))。
当 resolve() 或者 reject() 方法执行的时候,回调链中的回调函数会根据PromiseStatus 的状态情况而被依次调用。
顺带一提:axios 会自动转换 JSON 数据,所以 axios 只需要一次then能取出数据。