目录
Promise初识
Promise是异步编程的一种新的解决方案(以往都是用纯回调函数),它其实是一个构造函数,所以使用时要new出一个promise实例对象来;
通俗点讲,即Promise是一个构造函数,不是一个回调函数;它可以用来封装一个异步操作,并获取其结果~
同步回调与异步回调概念:
回调:程序员定义的,程序员没有调用,但是最终执行了;
同步回调:立即在主线程上执行的,不会放到回调队列里;如数组方法相关的回调函数/promise的执行器函数executor函数
异步回调:不会立即执行,放入回调队列中执行;如定时器回调/ajax回调/promise成功失败的回调
Promise实例对象有三种状态,分别是pending(进行中)、fullfilled(成功)以及rejected(失败),new出来的Promise实例初始状态均为pending;
Promise状态只能改变一次,即只能从pending改为fullfilled或从pending改为rejected;
new一个Promise:
<script>
const test = new Promise((resolve, reject) => {
setTimeout(() => {
resolve(200)
console.log('你是谁');
}, 2000)
})
</script>
Promise构造函数会接受一个参数,即executor函数,下面讲解下该函数;
executor函数
executor函数会接收两个参数,这两个参数均是函数,分别用形参resolve、reject表示;
- 调用resolve,会让Promise实例状态变为成功(fullfilled),同时指定成功的value;
- 调用reject,会让Promise实例状态变为失败(rejected),同时指定失败的reason;
注意,new出promise实例的同时,该函数同步调用(立即在主线程上执行)。即
(resolve,reject) => { }
是同步回调;但是executor函数的回调中,即{ }会指定异步代码;
Promise的API
Promise是一个构造函数,在原型上存在then和catch等方法,自身存在resolve、reject、all、race方法;
Promise.prototype.then方法
then方法返回一个新的Promise实例对象,它接收两个参数,一个是成功的参数(value),一个是失败的原因(error),这两个参数都是函数;
Promise.实例.then(onFullfilled, onRejected)
// onFullfilled是成功的回调函数,(value) => { }
// onRejected是失败的回调函数,(error)=> { }
then方法只有在promise实例状态发生改变后才会被调用(异步回调);
会返回一个新的Promise实例对象,它的状态和值由then()所指定的回调函数执行的结果决定;
如果then所指定的回调返回的是非Promise值a,则新的promise状态为fullfilled,成功的value值为a,
如果then所指定的回调返回的是Promise实例p,则新的promise状态和值与p的状态/值保持一致;
如果then所指定的回调抛出异常,则新的promise实例状态为rejected,reason为抛出的那个异常;
<script>
const p = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('a')
}, 1000)
})
const x = p.then(
value => { console.log('成功了1', value); return 999},
reason => {console.log('失败了1', reason);}
)
x.then(
value => { console.log('成功了2', value)},
reason => {console.log('失败了1', reason);}
)
// 控制台上输出结果 成功了1 a 成功了2 999
</script>
<script>
const p = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('a')
}, 1000)
})
const x = p.then(
value => { console.log('成功了1', value); return Promise.reject(999)},
reason => {console.log('失败了1', reason);}
)
x.then(
value => { console.log('成功了2', value)},
reason => {console.log('失败了2', reason);}
)
// 控制台上输出结果 成功了1 a 失败了2 999
</script>
Promise.prototype.catch方法
Promise.实例.catch(onRejected)
// onRejected是失败的回调函数,(error)=> { }
该方法其实是上述then方法的语法糖,相当于
Promise.实例.then(undefined1, onRejected)
Promise.reject方法
Promise.reject(reason)
该方法用于快速返回一个状态必为rejected的Promise实例对象,不管传入reason的值是Promise实例对象还是非Promise实例对象,结果都是一样;
const test = Promise.reject(900)
console.log(test); // Promise {<rejected>: 900}
<script>
const test = Promise.reject(900)
console.log(test); // Promise {<rejected>: 900}
const test1 = Promise.reject(test) // test是个失败的promise对象
test1.then(
value => {console.log('成功了', value) },
reason => {console.log('失败了',reason);} // 失败了 Promise {<rejected>: 900}
)
</script>
<script>
const test = Promise.resolve(900)
console.log(test); // Promise {<fulfilled>: 900}
const test1 = Promise.reject(test) // test是个成功的promise对象
test1.then(
value => {console.log('成功了', value) },
reason => {console.log('失败了',reason);} // 失败了 Promise {<rejected>: 900}
)
</script>
Promise.resolve方法
Promise.resolve(value)
该方法用于快速返回一个状态为fullfilled或rejected的Promise实例对象,此处value的值可以是非Promise实例对象或Promise实例对象;
若是非Promise实例对象(包括undefined以及null等),则返回的一定是个状态为fullfilled的Promise实例对象;
const test = Promise.resolve(900)
test.then(
value => {console.log('成功了', value) }, // 成功了 900
reason => {console.log('失败了',reason);}
)
若是Promise实例对象,则分情况,
- 若传入的是成功的Promise对象,则返回的也是fullfilled状态的Promise实例对象
<script>
const test = Promise.resolve(900)
console.log(test); // Promise {<fulfilled>: 900}
const test1 = Promise.resolve(test) // test是个成功的promise对象
test1.then(
value => {console.log('成功了', value) }, // 成功了 900
reason => {console.log('失败了',reason);}
)
</script>
- 若传入的是失败的Promise对象,则返回的是rejected状态的Promise实例对象
<script>
const test = Promise.reject(900)
console.log(test); // Promise {<rejected>: 900}
const test1 = Promise.resolve(test) // test是个失败的promise对象
test1.then(
value => {console.log('成功了', value) },
reason => {console.log('失败了',reason);} // 失败了 900
)
</script>
Promise.all方法
Promise.all(promiseArr)
该方法接收一个由Promise实例对象组成的数组做参数,返回一个新的Promise实例,当数组中所有的Promise实例对象均为fullfilled状态时,返回的新的Promise实例对象状态才为fullfilled,否则为rejected状态;若返回成功,则返回值是由每个Promise实例对象结果组成的数组,若返回失败,则返回值是该失败的Promise实例对象的结果;all中每个异步操作都是并行执行的,等到它们都执行完后才会进到then;
<script>
const test1 = new Promise((resolve, reject)=> {
setTimeout(() => {
console.log('test1');
resolve(200)
},1000)
})
const test2 = new Promise((resolve, reject)=> {
setTimeout(() => {
console.log('test2');
resolve(300)
},2000)
})
const test3 = new Promise((resolve, reject)=> {
setTimeout(() => {
console.log('test3');
resolve(400)
},2000)
})
const test4 = Promise.all([test1, test2, test3])
test4.then(
value => { console.log('成功了', value); }, // 成功了 [200, 300, 400]
reason => { console.log('失败了',reason); }
)
</script>
<script>
const test1 = new Promise((resolve, reject)=> {
setTimeout(() => {
console.log('test1');
resolve(200)
},1000)
})
const test2 = new Promise((resolve, reject)=> {
setTimeout(() => {
console.log('test2');
reject(300)
},2000)
})
const test3 = new Promise((resolve, reject)=> {
setTimeout(() => {
console.log('test3');
reject(400)
console.log('test5');
},2000)
})
const test4 = Promise.all([test1, test2, test3])
test4.then(
value => { console.log('成功了', value); },
reason => { console.log('失败了',reason); }// 失败了 300
)
</script>
上述代码依次在控制台上输出
可以结合后面宏队列微队列进行分析:
- test1/test2/test3定时器依次放入宏队列中;
- test4等待;
- test4.then回调放到test4自身,等待test4状态改变后,将回调函数推入微队列;
- 执行test1,输出test1,test1实例状态成功;
- 执行test2,输出test2,test2实例状态失败;
- 此时test4由于promise.all状态改变了(失败),则then回调推入微队列;
- 执行then微队列,输出’失败了‘ 300
- 执行test3定时器,依次输出test3、test5,并将状态置为失败
Promise.race方法
该方法同上,参数也是一个由Promise实例对象组成的数组,返回一个新的Promise实例对象,状态和结果取决于最先完成状态转变的那个Promise实例对象的状态和结果;与all方法不同的是,谁先执行完成就先执行回调。先执行完的不管是进行了race的成功回调还是失败回调,其余的将不会再进入race的任何回调
Promise.race(promiseArr)
<script>
const test1 = new Promise((resolve, reject)=> {
setTimeout(() => {
console.log('test1');
resolve(200)
},1000)
})
const test2 = new Promise((resolve, reject)=> {
setTimeout(() => {
console.log('test2');
reject(300)
},2000)
})
const test3 = Promise.race([test1, test2])
test3.then(
value => { console.log('成功了', value); }, // 成功了 200
reason => { console.log('失败了',reason); }
)
</script>
<script>
const test1 = new Promise((resolve, reject)=> {
setTimeout(() => {
console.log('test1');
resolve(200)
},1000)
})
const test2 = new Promise((resolve, reject)=> {
setTimeout(() => {
console.log('test2');
reject(300)
},200)
})
const test3 = Promise.race([test1, test2])
test3.then(
value => { console.log('成功了', value); },
reason => { console.log('失败了',reason); } // 失败了 300
)
</script>
then的链式调用
then的链式调用可以串联起多个异步任务(使用上篇总结中封装的axios发送请求):
<script>
import request from 'request' // 引入封装好的axios请求
// 将异步操作封装为promise,以便链式调用
function sendRequest(url, data) {
return new Promise((resolve, reject) => {
const result = request({url: url, data: data}) // 该处是接口请求,根据具体情况进行改动
if (result.code === 200) {
resolve(result.data) // 将成功的数据返回出去给后面成功的value使用
} else {
reject('接口报错了') // 返回的报错信息根据接口返回报错信息进行修改
}
})
}
sendRequest('url1', data1)
.then(value => {
console.log('第一次请求成功了', value); // 此处value值就是上面resolve传过来的result.data
return sendRequest('url2', data2); // 成功了则发送第二次请求
}, reason => {
console.log('第一次请求失败了', reason); // 此处value值就是上面resolve传过来的result.data
}).then(
value => {
console.log('第二次请求成功了', value); // 此处value值就是上面resolve传过来的result.data
return sendRequest('url3', data3); // 成功了则发送第三次请求
// 成功了则发送第二次请求,这个地方一定要return出去,因为then回调中,若返回非promise值则会包装成 成功的promise, 若返回promise实例(如此处的sendRequest('url2', data2))则结果与该实例保持一致,若不加return的话,则此处的箭头函数返回值就是undefined,则就都会走成功的回调了
}, reason => {
console.log('第二次请求失败了', reason); // 此处value值就是上面resolve传过来的result.data
}).then(
value => {
console.log('第三次请求成功了', value); // 此处value值就是上面resolve传过来的result.data
}, reason => {
console.log('第三次请求失败了', reason); // 此处value值就是上面resolve传过来的result.data
})
</script>
在使用链式调用时,可以不写每次的错误调用,直接在后面加上.catch方法,捕捉报错,此时只要任何一个.then方法报错,后面的then都不会再被执行(这种叫错误穿透)
<script>
import request from 'request' // 引入封装好的axios请求
// 将异步操作封装为promise,以便链式调用
function sendRequest(url, data) {
return new Promise((resolve, reject) => {
const result = request({url: url, data: data}) // 该处是接口请求,根据具体情况进行改动
if (result.code === 200) {
resolve(result.data) // 将成功的数据返回出去给后面成功的value使用
} else {
reject('接口报错了') // 返回的报错信息根据接口返回报错信息进行修改
}
})
}
sendRequest('url1', data1)
.then(value => {
console.log('第一次请求成功了', value); // 此处value值就是上面resolve传过来的result.data
return sendRequest('url2', data2);
// 成功了则发送第二次请求,这个地方一定要return出去,因为then回调中,若返回非promise值则会包装成 成功的promise, 若返回promise实例(如此处的sendRequest('url2', data2))则结果与该实例保持一致,若不加return的话,则此处的箭头函数返回值就是undefined,则就都会走成功的回调了
}).then(
value => {
console.log('第二次请求成功了', value); // 此处value值就是上面resolve传过来的result.data
return sendRequest('url3', data3); // 成功了则发送第三次请求
}).then(
value => {
console.log('第三次请求成功了', value); // 此处value值就是上面resolve传过来的result.data
}).catch(error => {
console.log('失败了', error);
})
</script>
几个关于promise的问题
如下几个问题是根据尚硅谷天禹老师梳理~感谢🙏
- 如何改变promise实例的状态?
(1)执行resolve(value),从pending变为fullfilled;
(2)执行reject(reason),从pending变为rejected;
(3)执行器函数executor抛出异常,则从pending变为rejected
<script>
const test1 = new Promise((resolve, reject)=> {
throw 900
})
test1.then(
value => { console.log('成功了', value); },
reason => { console.log('失败了',reason); } // 失败了 900
)
</script>
- 改变promise实例的状态和指定回调函数谁先谁后?
(1)正常情况下是先指定回调再改变状态,这种情况下回调函数先放到Promise实例对象上,等状态改变后,再将回调函数推入微队列执行;
(2)但是也可以先改变状态再指定回调,如延迟一会再调用回调(定时器里面加回调函数);
<script>
// 先指定回调,再改变状态
const test1 = new Promise((resolve, reject)=> {
setTimeout(() => {
resolve(200)
},1000)
})
test1.then(
value => { console.log('成功了', value); },
reason => { console.log('失败了',reason); }
)
</script>
<script>
// 先改变状态,再指定回调
const test1 = new Promise((resolve, reject) => {
resolve(200)
})
setTimeout(() => {
test1.then(
value => { console.log('成功了', value); },
reason => { console.log('失败了', reason); }
)
}, 1000)
</script>
- 如何中断Promise链?
当使用then的链式调用时,如果某个链状态变为rejected,不再执行后面的then回调函数;此种情况可以在失败的回调函数中返回一个pending状态的Promise实例;
<script>
import request from 'request' // 引入封装好的axios请求
// 将异步操作封装为promise,以便链式调用
function sendRequest(url, data) {
return new Promise((resolve, reject) => {
const result = request({url: url, data: data}) // 该处是接口请求,根据具体情况进行改动
if (result.code === 200) {
resolve(result.data) // 将成功的数据返回出去给后面成功的value使用
} else {
reject('接口报错了') // 返回的报错信息根据接口返回报错信息进行修改
}
})
}
sendRequest('url1', data1)
.then(value => {
console.log('第一次请求成功了', value); // 此处value值就是上面resolve传过来的result.data
return sendRequest('url2', data2);
}, reason => {
console.log('第一次请求失败了', reason);
return new Promise((resolve, reject) => {}) // 返回一个pending状态的promise
}).then(
value => {
console.log('第二次请求成功了', value); // 此处value值就是上面resolve传过来的result.data
return sendRequest('url3', data3); // 成功了则发送第三次请求
}, reason => {
console.log('第二次请求失败了', reason);
}).then(
value => {
console.log('第三次请求成功了', value); // 此处value值就是上面resolve传过来的result.data
}, reason => {
console.log('第三次请求失败了', reason);
})
</script>
async/await
该方法是处理异步请求的终极法宝,使用它可避免promise的then链式调用(底层还是用的then链式调用,但是我们看不到喽),仅用几行代码即可实现同样的功能;
await右侧的表达式一般为Promise实例对象,但也可以是其它的值;
- 若是其它非promise值,则直接将此值作为await的返回值;
- 若是Promise实例对象,则返回的是该实例成功后的值;
await必须写在async函数中,但反之,async函数中可以没有await,但是没有意义哈哈哈;
若await右侧的表达式执行失败,则会抛出异常,通常配合try...catch使用;
该方法虽然表面上看不见任何回调函数,但其实程序执行时会将其转变为回调函数执行;
<script>
// await/async写法,依次输出a,b
const test = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('a')
}, 2000)
})
async function demo() {
const test1 = await test
console.log(test1);
console.log('b');
}
demo()
</script>
<script>
// 后台转换,依次输出a,b
const test = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('a')
}, 2000)
})
test.then((test1) => {
console.log(test1);
console.log('b');
})
</script>
宏任务与微任务
又称为宏队列与微队列,其中除了promise的then回调外,大部分都要被推入宏队列,在执行宏队列时要先检查微队列中是否有任务,如有,则要先执行微任务,没有才依次执行宏任务
<script>
// 依次输出 主线程 2 3 1
setTimeout(() => {
console.log(1);
}, 1000)
Promise.resolve(2).then(
value => {console.log(value);}
)
Promise.resolve(3).then(
value => {console.log(value);}
)
console.log('主线程');
</script>
console.log(1)
setTimeout(function(){
console.log(2)
}, 0)
new Promise(function(resolve){ //这里的回调是同步的
console.log(3)
resolve()
}).then(function(){ //异步微任务
console.log(4)
})
console.log(5)
//输出结果 1,3,5,4,2
总结promise基本的编码流程:
- 创建Promise的实例对象(此时为pending状态),传入executor函数
- 在executor函数中开启异步任务(定时器/ajax请求/axios请求)
- 根据异步任务的结果,做不同的处理;
- 若异步任务成功了,则调用resolve(value),让promise实例对象状态变为成功(fullfilled) ,同时指定成功的value;
- 若异步任务失败了,则调用reject(reason),让promise实例对象状态变为失败(rejected) ,同时指定成功的reason;
- 通过then方法为promise指定成功/失败的回调函数,来获取成功的value/失败的reason(then方法指定的成功/失败的回调都是异步回调)