Promise
是个啥?
马上拿起来了一个有道词典
查了一下.
有承诺,保证
的意思.
很多电影里都有这句台词:I Promise You.
是什么问题导致了ES6要给一个Promise?
在开发web应用的过程中,AJAX
异步请求数据很常见.
$.get('./data/users/1',function(data){
//.....
})
一个页面请求多个接口,每个接口之间有相互依赖关系也很常见.
$.get('./data/users/1',function(data){
$.get('./data/products/${data.userid}',function(data){
$.get('./data/accountInfo/${data.productAccountInfo}',function(data){
//......
})
})
})
有问题吗? callback
被丢到了 Event Loop
里了,执行时机除了它自己,没人能知道.所以,为了获取上一个接口的数,我们需要在它里面在发送下一个 Ajax
请求.
强行问题在于:如果这样的接口依赖很多呢?
所谓的callback hell --- 回调地狱??
对,代码从原来的线性自上而下,变成了现在的横向发展.
但不管它们变成什么样.
起码在上述和我简单的demo中的代码,有几个共同点
- 都是 callback 回调的方法.
- 都是下一个接口请求依赖上一个接口返回的数据.
Promise 如何解决代上述代码横向发展的问题的?
为了解决代码丑陋和难以维护(代码写的太臃肿,太丑了,确实也就变得很难维护了)问题.
于是 ES6 新推出了一个叫 Promise 的玩意.
感性认知一下
userDataPromise('./data/users/1')
.then((data)=>{
// 在这里拿到了用户数据
return productsDataPrmoise('./data/products/${data.userid}')
})
.then((data)=>{
// 在这里拿到的商品信息
return accountDataPromise('./data/accountInfo/${data.productAccountInfo}')
})
.then((data)=>{
// 在这里拿到了账户信息,然后该做什么就作什么.
//.....
})
这个和上述使用 $.get
的共同点.
- 都是一个请求发完之后,接着发下一个请求.下一个请求依赖上一个请求的数据.
- 每个
Promise
使用then???
来执行回调函数. - 代码的结构是很舒服的纵向发展.
这个和上述 $.get
的不同点
-
$.get
的回调函数,我们是在一个方法(,function(){callbackhere})
里写的. - 第二个
$.get
嵌套在了第一个$.get的callback
里面. - 代码是横向发展的
仅仅是代码从嵌套横向变成了then纵向了而已啊?那我把回调函数放在外面赋值,不给里面不就行了?
function get(url) {
var oReq = new XMLHttpRequest()
// oReq.onload = function () {
// callback(JSON.parse(oReq.responseText))
// }
oReq.open('GET', url, true)
oReq.send()
return oReq // 返回这个是为了拿到 onload & oReq.responseText
}
var xhr = get('./data/1.json')
// 把异步callback注册到Event Loop 的操作仍然是同步的,t同步的再慢,也比异步的要快.我就不相信会出现,我xhr2 还没执行完,xhr 的 onload 回调函数就执行了的情况!!!
// 同步代码的执行优先权永远大于异步执行代码.
xhr.onload = ()=>{
console.log(JSON.parse(xhr.responseText))
}
var xhr2 = get('./data/2.json')
xhr2.onload = () => {
console.log(JSON.parse(xhr2.responseText))
}
var xhr3 = get('./data/3.json')
xhr3.onload = () => {
console.log(JSON.parse(xhr3.responseText))
}
// 对比
$.get('./data/users/1',function(data){
$.get('./data/products/${data.userid}',function(data){
$.get('./data/accountInfo/${data.productAccountInfo}',function(data){
//......
})
})
})
// 无非就是把异步函数注册代码的步骤平移了出来,仅此而已.
Promise
的出现原因之一,就是为了让我们不要在写那种 callback hell
那样的代码结构了吗?
Promise 的基本使用.
Promise
是解决啥的?
解决多个异步回调函数之间嵌套写法的问题
意思就说,如果没有异步,都是同步的代码,就用不上Promise了
所以,用Promise主要是用在异步上.
可以Promise想象成一个异步操作的容器.
Promise承诺你,当这个异步完成之后,不管成功还是失败,只要你指定了对应的回调函数,我都会去执行.
是不是感觉很废?
- 异步操作,我要装在你里面去
- 什么是成功,什么是失败我也要告诉你.
但为了解决多个异步嵌套导致代码横向扩展的问题,咱们还是去用吧.毕竟逼格高一点.
- 首先我们需要实例化一个
Promise对象
(对Promise是一个构造函数)
new Promise()
- 此构造函数接受一个函数作为参数.
new Prmoise(function....)
- 此函数参数包含两个形参(reslove,reject)
new Promise(function(reslove,reject){})
- 最后是完全体
new Promise(function(reslove,reject){
$.get('./data/users/1',function(data){
if (data.success) reslove(data)
reject(data.error)
})
})
- 要把异步操作给
Promise
==>$.get('./data/users/1'
- 告诉
Promise
啥是成功 ==>reslove(data)
- 啥是失败 ==>
reject(data.error)
接着就是使用我们刚new出来的Promise
.
既然是new出来的,我们就拿个对象去接受.
var p = new Promise(function(reslove,reject){
$.get('./data/users/1',function(data){
if (data.success) reslove(data)
reject(data.error)
})
})
我们在给Promise传参的时候,指定了 reslove & reject 两个函数的形参,如何传递这两个的实参呢?
p.then((data)=>{},(err)=>{})
使用实例对象的 then
方法,接受两个参数,顺序是 then(resloveCallback,rejectCallback)
最后完整的使用,并发送请求.
var p = new Promise(function(reslove,reject){
$.get('./data/users/1',function(data){
if (data.success) reslove(data)
reject(data.error)
})
})
p.then((data)=>{
console.log(data) // 数据请求成功
},(err)=>{
console.log(err) // 数据请求失败.
})
到这一步是不是很无聊?
无非就是提供了一个then
方法,让我们来指定成功和失败的回调函数实参.....
Promise 的一些其他特性.
官方说明:
Promise 是异步编程的一种解决方案,比传统的解决方案——回调函数和事件——更合理和更强大。它由社区最早提出和实现,ES6 将其写进了语言标准,统一了用法,原生提供了Promise对象。
Promise对象有以下两个特点。
- 对象的状态不受外界影响。Promise对象代表一个异步操作,有三种状态:pending(进行中)、fulfilled(已成功)和rejected(已失败)。只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态。这也是Promise这个名字的由来,它的英语意思就是“承诺”,表示其他手段无法改变。
- 一旦状态改变,就不会再变,任何时候都可以得到这个结果。Promise对象的状态改变,只有两种可能:从pending变为fulfilled和从pending变为rejected。只要这两种情况发生,状态就凝固了,不会再变了,会一直保持这个结果,这时就称为 resolved(已定型)。如果改变已经发生了,你再对Promise对象添加回调函数,也会立即得到这个结果。这与事件(Event)完全不同,事件的特点是,如果你错过了它,再去监听,是得不到结果的。
根据文档画一张很无聊的图.
Promise 的基本套路
还是直接上代码来的实际.
由于 Promise 的 then 方法,又返回了一个 Promise 对象的类型,所以,Promise对象可以不停的then.
这就为 Promise 对象链式调用提供了基础.
function createReslovePromise() {
return new Promise(function (reslove,reject) {
setTimeout(function () {
reslove(data)
},1000)
})
}
createReslovePromise()
.then(() => {
return 1
})
.then((data) => {
console.log(data)
return 2
})
.then((data) => {
console.log(data)
})
relax-2:测试 relax$ node Promise.js
1
2
relax-2:测试 relax$
图解:
上一个 then
中reslove
的返回值,就是下一个then
中reslove
的参数
但是如果,,我在上一个Promise对象
的then
的reslove
中,成功接受到了成功数据之后,就返回下一个 Promise对象
.
那么下一个then就是上一个返回的Promise对象的then了.
function createReslovePromise(stepName) {
return new Promise(function (reslove,reject) {
setTimeout(function () {
reslove(stepName)
},1000)
})
}
createReslovePromise('step 01')
.then((data) => {
console.log(data) // 数据收到了,返回下一个Promise
return createReslovePromise('step 02')
})
.then((data) => {
console.log(data)
return createReslovePromise('step 03')
})
.then((data) => {
console.log(data)
})
relax-2:测试 relax$ node Promise.js
step 01
step 02
step 03
图解
基于上面这个then
返回 Promise
的特性,就可以完成链式的之上而下的异步代码结构了.
使用Promise请求一个瞎编的逻辑
- 首先请求
./data/users/1
拿到id为1的用户 - 接着请求
./data/preferences/{user.preferences}
在根据拿到用户的preferences
拿到用户的偏好设置里面的bobbies
- 最后根据用户的
preferences.hobbies
拿到用户的爱好.../data/hobbies/{preferences.hobbies}
{
"users":[
{"id":1,"name":"张三","preferences":1},
{"id":2,"name":"李四","preferences":2},
{"id":3,"name":"王五","preferences":3},
{"id":4,"name":"赵六","preferences":4}
],
"preferences":[
{"id":1,"hobbies":1},
{"id":2,"hobbies":2},
{"id":3,"hobbies":3},
{"id":4,"hobbies":4}
],
"hobbies":[
{"id":1,"values":["看书,打游戏,听歌,骑行"]},
{"id":2,"values":["看书,打游戏,听歌,骑行"]},
{"id":3,"values":["看书,打游戏,听歌,骑行"]},
{"id":4,"values":["看书,打游戏,听歌,骑行"]}
]
}
启动一个 json-server
服务
json-server --watch db.json
服务启动成功
测试一下
使用传统的 $.get 方式
$.get('http://localhost:8000/users/1',function(data){
let userInfo = data
console.log(data)
$.get(`http://localhost:8000/preferences/${userInfo.preferences}`,function(data){
console.log(data)
let userPreferences = data
$.get(`http://localhost:8000/hobbies/${userPreferences.hobbies}`,function(data){
let userHobbies = data
console.log(data)
var hobbies = userHobbies.values && userHobbies.values.join(',')
document.querySelector('h1').innerText = hobbies
})
})
})
好像有callback hell
回调地狱了.
使用原生XMLHttpRequest
并回调函数平移出来的方式
var xhr = get('http://localhost:8000/users/1')
xhr.onload = function () {
let jsonData = JSON.parse(xhr.responseText)
let userInfo = jsonData
console.log(jsonData)
var xhr2 = get(`http://localhost:8000/preferences/${userInfo.preferences}`)
xhr2.onload = () => {
let jsonData = JSON.parse(xhr2.responseText)
let userPreferences = jsonData
console.log(userPreferences)
var xhr3 = get(`http://localhost:8000/hobbies/${userPreferences.hobbies}`)
xhr3.onload = () => {
let jsonData = JSON.parse(xhr3.responseText)
let userHobbies = jsonData
var hobbies = userHobbies.values && userHobbies.values.join(',')
document.querySelector('h1').innerText = hobbies
}
}
}
function get(url) {
let xhr = new XMLHttpRequest()
xhr.open('GET', url, true)
xhr.send()
return xhr
}
突然发现问题出来了,如果有依赖性的接口请求关系,不管怎么把回调函数移出来,最后还是会被迫的写成 callback hell
的形式.
最后在使用 Promise
function pGet(url) {
return new Promise(function (reslove, reject) {
get(url, function (data) {
reslove(data)
})
})
}
function get(url, callback) {
let xhr = new XMLHttpRequest()
xhr.open('GET', url, true)
xhr.send()
xhr.onload = function () {
callback && typeof callback === 'function' && callback(JSON.parse(xhr.responseText))
}
}
pGet('http://localhost:8000/users/1')
.then(function(data){
console.log(data)
let userInfo = data
return pGet(`http://localhost:8000/preferences/${userInfo.preferences}`)
})
.then((data)=>{
console.log(data)
let preferences = data
return pGet(`http://localhost:8000/hobbies/${preferences.hobbies}`)
})
.then((data)=>{
console.log(data)
let hobbies = data
var hobbiesStr = hobbies && hobbies.values instanceof Array && hobbies.values.join(',')
document.querySelector('h1').innerText = hobbiesStr
})
发现代码最终是按照竖向的走向往下写的.
确实是避免了代码横向走的问题.
使用Promise确实可以比较优雅的写出有依赖关系接口方法的代码层级结构.所以,它确实解决了callback hell
写法的问题.