一线大厂高级前端编写,前端初中阶面试题,帮助初学者应聘,需要联系微信:javadudu

ES6 – Promise

Promise:对异步操作的状态管理

基本使用方法

// resolve 成功
// reject  失败
let p = new Promise((resolve, reject) => {
   setTimeout(() => {
       console.log('ES6之Promise');
       // resolve()
       // reject()
       // if(){
       //     resolve()
       // } else {
       //     reject()
       // }
       
   }, 1000);
}).then(() => {
   console.log('成功1');
}, () => {
   console.log('失败1');
})
  • then(()=>{// success},()=>{// fail})then接收2个回调作为参数,第一个是成功回调,第二个是失败回调,第一个参数必传,第二个参数可以省略
  • Promise的参数是一个回调,回调的的2个参数是函数,第一个参数resolve表示回调成功后执行的方法,第二个参数表示回调失败后执行的方法,当调用resolve的时候会进入then的第一个方法里,当调用reject的时候会进入then的第二个方法里
  • 如果异步的结果成功了,需要手动的调用下resolve(),这样才能进入下一个then的成功回调
  • 如果异步的结果失败了,需要手动的调用下reject(),这样才能进入下一个then的失败回调
  • 如果后面的异步操作想得到上一步的异步操作的结果,成功了就将成功的结果放到成功回调resolve(成功的结果)的参数中,在then的成功回调中用参数接收,如果失败了就将失败的结果放到失败回调reject(失败的结果)的参数中,在then的失败回调中参数接收

Promise与异步的关系

Promise和异步没有任何关系!

Promise中也可以放同步的代码,eg:

let p = new Promise((resolve,reject) => {
    console.log(1)
    resolve()     // 如果想进入then,必须手动调用then/reject
})
console.log(2);
p.then(res => {
    console.log(3);
})                 // 输出结果: 1  2  3

只不过大部分时候,Promise都会结合异步的操作去使用,否则就失去了Promise的意义了

new Promise里面的代码会被立即执行

then相当于是Promise的微任务,而正常的代码是一个宏任务,JS中会先进行宏任务再执行微任务

Promise的三种状态

三种状态

ES6 - Promise

Promise中只有三种状态:当 new Promise的时候它的状态是pending(进行中);如果异步执行结果成功就调用resolve(),状态变为fulfiled(已成功);如果异步执行结果失败就调用reject(),状态变为rejected(已失败)

Promise的这三种状态并不受外界的影响,只有当前异步处理的结果resolve/reject,才会决定当前Promise到底处于哪种状态,其他的操作都没办法改变它,它的状态是不可逆的

eg:

let p1 = new Promise((resolve,reject)=>{
    resolve(1)
})
let p2 = new Promise((resolve,reject)=>{
    setTimeout(() => {
        resolve(2)  
    }, 1000);
})
let p3 = new Promise((resolve,reject)=>{
    setTimeout(() => {
        reject(3)  
    }, 1000);
})
console.log(p1);                 // resolved
console.log(p2);                 // pending
console.log(p3);                 // pending
// p2、p3 resove或者reject改变状态之后的结果
setTimeout(() => {
    console.log(p2);                 
    
}, 2000);
setTimeout(() => {
    console.log(p3);                 
    
}, 2000);
// then中拿到Promise的结果
p1.then(res => {
    console.log(res);
})
p2.then(res => {
    console.log(res);
})
p3.catch(err => {
    console.log(err);
})

为什么Promise外面的状态和里面的状态不一致?

ES6 - Promise

为什么输出的p2外面的Promise的状态是pending,而里面的状态又是fulfiled?

因为当我们写了new Promise(...)之后会立即执行它里面的代码,但是此时还没有resolve(),resolve()是在1秒后执行,因此转态先是pending,1秒后状态变成fulfiled

其实当我们在1秒之前打开,结果里p2的Promise的状态也显示是pending状态,注意不管是外面的还是里面的Promise的状态都是同一个P2的状态,我们只需要在意外面的状态就行

ES6 - Promise

p2、p3 resove或者reject改变状态之后的结果

如果我们想看p2、p3 resove或者reject改变状态之后的结果,不妨加个定时器,2秒后,p2的resole已经执行,Promise的状态已被改变。2秒后,p3的reject已经执行,Promise的状态也已被改变。

ES6 - Promise

then中拿到Promise的结果

失败的回调函数可以放到then的第二个参数中,或者使用catch,catch相当于只有失败回调的then

为什么Promise的状态是不可逆的?

let p = new Promise((resolve, reject) => {
    resolve(1)
    reject(2)
})

p.then(success => {
    console.log(success);
},err => {
    console.log(err);
})

//等价于下面的写法
p.then(success => {
    console.log(success);
}).catch(err => {
    console.log(err);
})

结果是只输出1,2并没有被输出

ES6 - Promise

为什么这样?因为Promise就有“承诺/一诺千金”的意思,当前的Promise的状态如果由pending,经过resolve()变成fulfiled,那么状态就已经被“凝固”了,即使后面再调用reject那么状态也不可逆、不可被改变

使用Promise改造Callback Hell 的例子

原始的方式

1. 错误的写法

function ajax(url, callback) {
    // 1. 创建XMLHttpRequest对象
    var xtmlhttp
    if (window.XMLHttpRequest) {
        xmlhttp = new XMLHttpRequest()
        // 兼容早期浏览器
    } else {
        xmlhttp = new ActiveXObject('Microsoft.XMLHTTP')
    }
    // 2. 发送请求
    xmlhttp.open('GET', url, true)
    xmlhttp.send()
    // 3. 接收服务端相应
    xmlhttp.onreadystatechange = function () {
        if (xmlhttp.readyState === 4 && xmlhttp.status === 200) {
            var obj = JSON.parse(xmlhttp.responseText)
            callback(obj);
        }
    }

}

new Promise((resolve,reject) => {
    ajax('static/a.json',res => {
        console.log(res);
        resolve()
    })
}).then(res => {
    console.log('a读取成功');
    new Promise((resolve,reject)=>{
        ajax('static/b.json',res => {
            console.log(res);
            resolve()
        })
    })
}).then(res => {
    console.log('b读取成功');
})

看下运行的结果:

ES6 - Promise

结果输出的顺序好像不对,不应该:a的结果 -> a读取成功 -> b的结果 -> b读取成功吗?

第二个then原本是想对第一个then返回的Promise进行链式操作,接着then,但是由于第一个then中的并没有返回值(没有写return),因此第二个then相当于对一个空的Promise对象进行then的操作

如果想进行链式操作,我们需要将Promise对象return出去(return出去的Promise对象相当于得到了一个新的Promise对象),以供后面的then继续的then,进行链式操作

2.修正后的写法

function ajax(url, callback) {
    // 1. 创建XMLHttpRequest对象
    var xtmlhttp
    if (window.XMLHttpRequest) {
        xmlhttp = new XMLHttpRequest()
        // 兼容早期浏览器
    } else {
        xmlhttp = new ActiveXObject('Microsoft.XMLHTTP')
    }
    // 2. 发送请求
    xmlhttp.open('GET', url, true)
    xmlhttp.send()
    // 3. 接收服务端相应
    xmlhttp.onreadystatechange = function () {
        if (xmlhttp.readyState === 4 && xmlhttp.status === 200) {
            var obj = JSON.parse(xmlhttp.responseText)
            callback(obj);
        }
    }

}

new Promise((resolve,reject) => {
    ajax('static/a.json',res => {
        console.log(res);
        resolve()
    })
}).then(res => {
    console.log('a读取成功');
    return new Promise((resolve,reject)=>{
        ajax('static/b.json',res => {
            console.log(res);
            resolve()
        })
    })
}).then(res => {
    console.log('b读取成功');
})

输出的结果的顺序也符合预期了

ES6 - Promise

3. 再把读取c.json加进来

function ajax(url, callback) {
    // 1. 创建XMLHttpRequest对象
    var xtmlhttp
    if (window.XMLHttpRequest) {
        xmlhttp = new XMLHttpRequest()
        // 兼容早期浏览器
    } else {
        xmlhttp = new ActiveXObject('Microsoft.XMLHTTP')
    }
    // 2. 发送请求
    xmlhttp.open('GET', url, true)
    xmlhttp.send()
    // 3. 接收服务端相应
    xmlhttp.onreadystatechange = function () {
        if (xmlhttp.readyState === 4 && xmlhttp.status === 200) {
            var obj = JSON.parse(xmlhttp.responseText)
            callback(obj);
        }
    }

}

new Promise((resolve,reject) => {
    ajax('static/a.json',res => {
        console.log(res);
        resolve()
    })
}).then(res => {
    console.log('a读取成功');
    return new Promise((resolve,reject)=>{
        ajax('static/b.json',res => {
            console.log(res);
            resolve()
        })
    })
}).then(res => {
    console.log('b读取成功');
    return new Promise((resolve,reject)=>{
        ajax('static/c.json',res => {
            console.log(res);
            resolve()
        })
    })
}).then(res => {
    console.log('c读取成功');
})

输出的结果:

ES6 - Promise

看下之前回调深渊的写法:

ajax('static/a.json',res =>{
    console.log(res)           // {a: '我是A'}
    ajax('static/b.json',res => {
        console.log(res);     // {b: '我是B'}
        ajax('static/c.json',res => {
            console.log(res); // {c: '我是C'}
        })
    })
})

上面的写法层层嵌套

使用Promise改写之后,就将之前层层嵌套的方式,改为扁平式的,代码更利于阅读和维护,也更好的管理失败后做什么,失败后做什么

但是可能会产生疑问:这样代码变得更多了

改造的方式

我们发现有很多重复的部分,每次都是返回一个Promise对象,在Promise对象中进行Ajax请求,我们不妨封装成函数

function ajax(url, callback) {
    // 1. 创建XMLHttpRequest对象
    var xtmlhttp
    if (window.XMLHttpRequest) {
        xmlhttp = new XMLHttpRequest()
        // 兼容早期浏览器
    } else {
        xmlhttp = new ActiveXObject('Microsoft.XMLHTTP')
    }
    // 2. 发送请求
    xmlhttp.open('GET', url, true)
    xmlhttp.send()
    // 3. 接收服务端相应
    xmlhttp.onreadystatechange = function () {
        if (xmlhttp.readyState === 4 && xmlhttp.status === 200) {
            var obj = JSON.parse(xmlhttp.responseText)
            callback(obj);
        }
    }

}
function getPromise(url) {
    return new Promise((resolve, reject) => {
        ajax(url, res => {
            resolve(res)
        })
    })
}
getPromise('static/a.json')
    // then里面的结果res都是通过函数getPromise中resolve传递过来的
    .then(res => {
        console.log(res);
        return getPromise('static/b.json')
    }).then(res => {
        console.log(res);
        return getPromise('static/c.json')
    }).then(res => {
        console.log(res);
    })

ES6 - Promise

这样代码就变得更加的扁平化、语义化,便于阅读和对结果进行管理,这就是Promise的好处

再加入对失败结果的处理吧

function ajax(url, successCallback, failCallback) {
    // 1. 创建XMLHttpRequest对象
    var xtmlhttp
    if (window.XMLHttpRequest) {
        xmlhttp = new XMLHttpRequest()
        // 兼容早期浏览器
    } else {
        xmlhttp = new ActiveXObject('Microsoft.XMLHTTP')
    }
    // 2. 发送请求
    xmlhttp.open('GET', url, true)
    xmlhttp.send()
    // 3. 接收服务端相应
    xmlhttp.onreadystatechange = function () {
        if (xmlhttp.readyState === 4 && xmlhttp.status === 200) {
            var obj = JSON.parse(xmlhttp.responseText)
            successCallback && successCallback(obj);
        } else if (xmlhttp.readyState === 4 && xmlhttp.status === 404) {
            failCallback && failCallback(xmlhttp.statusText)
        }
    }

}
function getPromise(url) {
    return new Promise((resolve, reject) => {
        ajax(url, res => {
            resolve(res)
        }, err => {
            reject(err)
        })
    })
}
getPromise('static/aa.json')
    .then(res => {
        console.log(res);
        return getPromise('static/b.json')
    }, err => {
        console.log(err);
    }).then(res => {
        console.log(res);
        return getPromise('static/c.json')
    }).then(res => {
        console.log(res);
    })

看下运行结果:

ES6 - Promise

  • Promise中不会因为第一个异步操作的结果失败而影响第二个、第三个异步操作,第二个、第三个异步操作并没有停止执行,也执行了
  • 为什么输出undefined?
    ==> Promise中发起的ajax请求的路径’static/aa.json’并不存在,因此第一个then会进入失败回调,然而第一个then中的失败回调并没有返回任何的Promise对象,相当于返回一个空的Promise对象,而第二个then是对第一个then失败结果返回的Promise进行then,所以第二个then中输出undefined。或者说第二个then中的res是第一个then的成功回调的结果,但是由于第一个then并没有执行成功回调而是执行失败回调,所以第二个then中的成功回调res结果是undefined
  • 为什么还能正确的输出{c:'我是C'}?
    ==>因为它的上一个then中对’static/c.json’发起了ajax请求,并成功的返回了Promise对象(返回了成功函数的结果)

aa.json读取失败但是不要影响b.json的读取

aa.json读取失败后,也继续返回Primise对象,对’static/b.json’发起ajax请求

getPromise('static/aa.json')
    .then(res => {
        console.log(res);
        return getPromise('static/b.json')
    }, err => {
        console.log(err);
        // a.json读取失败但是不要影响b.json的读取 
        return getPromise('static/b.json')
    }).then(res => {
        console.log(res);
        return getPromise('static/c.json')
    }).then(res => {
        console.log(res);
    })

输出的结果

ES6 - Promise

不对失败的结果进行单独的处理,而是进行统一的处理

function ajax(url, successCallback, failCallback) {
    // 1. 创建XMLHttpRequest对象
    var xtmlhttp
    if (window.XMLHttpRequest) {
        xmlhttp = new XMLHttpRequest()
        // 兼容早期浏览器
    } else {
        xmlhttp = new ActiveXObject('Microsoft.XMLHTTP')
    }
    // 2. 发送请求
    xmlhttp.open('GET', url, true)
    xmlhttp.send()
    // 3. 接收服务端相应
    xmlhttp.onreadystatechange = function () {
        if (xmlhttp.readyState === 4 && xmlhttp.status === 200) {
            var obj = JSON.parse(xmlhttp.responseText)
            successCallback && successCallback(obj);
        } else if (xmlhttp.readyState === 4 && xmlhttp.status === 404) {
            failCallback && failCallback(xmlhttp.statusText)
        }
    }

}
function getPromise(url) {
    return new Promise((resolve, reject) => {
        ajax(url, res => {
            resolve(res)
        }, err => {
            reject(err)
        })
    })
}
getPromise('static/aa.json')
    .then(res => {
        console.log(res);
        return getPromise('static/b.json')
    }).then(res => {
        console.log(res);
        return getPromise('static/c.json')
    }).then(res => {
        console.log(res);
    }).catch(err => {
        console.log(err);
    })

运行的结果:

ES6 - Promise

这样如果出现了失败就直接进入catch,错误之前的then就不会再进入

原文链接:https://juejin.cn/post/7234058546572951612 作者:仗剑走天涯_

(0)
上一篇 2023年5月18日 上午10:05
下一篇 2023年5月18日 上午10:16

相关推荐

发表评论

登录后才能评论