一、概念
1.Promise 是异步编程的一种解决方案,比传统的解决方案——回调函数和事件,更合理和强大
2.Promise对象的特点:
- 对象的状态不受外界影响(三种状态:pending(进行中)、 fullfilled(已成功)、rejected(已失败))
- 一旦状态发生改变,就不会再变,任何时候都可以得到这个结果(pending -> fulfilled, pending ->rejected, resolved定型)
3.Promise的缺点:
- 无法取消Promise,一旦建立它就会立即执行,无法中途取消
- 如果不设置回调函数,Promise内部抛出的错误,不会反应到外部
- 当处于pending状态时,无法得知目前进展到哪个阶段(刚刚开始还是即将完成)
4.列子和补充
console.log("a");
setTimeout(() => console.log("b"));
let p = new Promise((resolve, reject) => {
resolve();
console.log("c");
}).then(() => {
console.log("d");
});
console.log("e");
输出顺序:a->c->e->d->b
1.JS有一个宏任务队列,有一个微任务队列,执行循环,当执行栈为空,先清空微任务,在执行宏任务.
2.then仅仅是把第一个注册到onfulfilled,第二个注册到rejected.
3.setTimeout第一个函数是当定时时间到执行的回调函数.
4.一个Promise执行resolve时,状态改变为fulfilled,值改变为resolve传入的值.
二、原理
原理上Promise 也还是使用回调函数,只不过是把回调封装在了内部,使用上一直通过 then 方法的链式调用,使得多层的回调嵌套看起来变成了同一层的,书写上以及理解上会更直观和简洁一些。
new Promise((resolve, reject) => {
setTimeout(() => {
resolve({ test: 1 })
resolve({ test: 2 })
reject({ test: 2 })
}, 1000)
}).then((data) => {
console.log('result1', data)
},(data1)=>{
console.log('result2',data1)
}).then((data) => {
console.log('result3', data)
})
输出结果:
result1 { test: 1 }
result3 undefined
显然这里输出了不同的 data。由此可以看出几点:
- 可进行链式调用,且每次 then 返回了新的 Promise(2次打印结果不一致,如果是同一个实例,打印结果应该一致。
- 只输出第一次 resolve 的内容,reject 的内容没有输出,即 Promise 是有状态且状态只可以由pending -> fulfilled或 pending-> rejected,是不可逆的。
- then 中返回了新的 Promise,但是then中注册的回调仍然是属于上一个 Promise 的。
三、用法以及应用场景
异步编程
通常来说,程序都是顺序执行的,同一时刻只会发生一件事。如果一个函数依赖于另一个函数的结果,它只能等待那个函数结束才能继续执行,从用户的角度来说,整个程序才算运行完毕。
- 异步执行的运行机制
(1)所有同步任务都在主线程上执行,形成一个执行栈(execution context stack)。
(2)主线程之外,还存在一个"任务队列"(task queue)。只要异步任务有了运行结果,就在"任务队列"之中放置一个事件。
(3)一旦"执行栈"中的所有同步任务执行完毕,系统就会读取"任务队列",看看里面有哪些事件。那些对应的异步任务,于是结束等待状态,进入执行栈,开始执行。
(4)主线程不断重复上面的第三步。
只要主线程空了,就会去读取"任务队列",这就是JavaScript的运行机制。这个过程会不断重复。
2.异步编程与Promise的关系:
Promise对象可以理解为一次执行的异步操作,使用promise对象之后可以使用一种链式调用的方式来组织代码;让代码更加的直观。 也就是说,有了Promise对象,就可以将异步操作以同步的操作的流程表达出来,避免了层层嵌套的回调函数。 总结一下就是可以将原先不可控的回调通过promise转为更加可控更清晰的方式表达,更加高效,更便于维护。
Promise的作用与用法
基本用法
Promise使用实例:
function asyncDo(){
return new Promise(function(resolve,reject){
// some codes
if(/*异步操作成功*/){
resolve(value); //异步操作成功,将value传递出去
}else{
reject(erroe); //异步操作失败,报出错误,将error传递出去
}
});
}
链式调用
let Promise1 = new Promise((resolve, reject) => {
setTimeout(() => {
let t = true;
if (t) {
resolve("success");
} else {
reject("failed");
}
}, 1000);
});
Promise1.then((r) => {
console.log(r);
return r + "1";
})
.then((r) => {
console.log(r);
return r + "2";
})
.then((r) => {
console.log(r);
});
//success
//success1
//success12
错误传递
new Promise((resolve, reject) => {
console.log("in Promise");
resolve();
})
.then(() => {
throw new Error("then Error");
console.log("then");
})
.then((r) => {
console.log("then continue");
})
.catch((err) => {
console.log("catch error" + err.message);
});
//in Promise
//catch errorthen Error
利用catch来终止Promise链,避免链条中的rejection抛出错误到全局
Promise在实际环境下的应用
promise.all方法
var p1 = new Promise(function (resolve) {
setTimeout(function () {
resolve("第一个promise");
}, 3000);
});
var p2 = new Promise(function (resolve) {
setTimeout(function () {
resolve("第二个promise");
}, 1000);
});
Promise.all([p1, p2]).then(function (result) {
console.log(result); // ["第一个promise", "第二个promise"]
});
上面的代码中,all接收一个数组作为参数,p1,p2是并行执行的,等两个都执行完了,才会进入到then,all会把所有的结果放到一个数组中返回,所以我们打印出我们的结果为一个数组。值得注意的是,虽然p2的执行顺序比p1快,但是all会按照参数里面的数组顺序来返回结果
应用场景1:多个请求结果合并在一起
具体描述:一个页面,有多个请求,我们需求所有的请求都返回数据后再一起处理渲染
应用场景2:合并请求结果并处理错误
描述:我们需求单独处理一个请求的数据渲染和错误处理逻辑,有多个请求,我们就需要在多个地方写
应用场景3:验证多个请求结果是否都是满足条件
描述:在一个微信小程序项目中,做一个表单的输入内容安全验证,调用的是云函数写的方法,表单有多7个字段需要验证,都是调用的一个 内容安全校验接口,全部验证通过则 可以 进行正常的提交
promise.race方法
race的意思为赛跑,因此,promise.race也是传入一个数组,但是与promise.all不同的是,race只返回跑的快的值,也就是说result返回比较快执行的那个。
var p1 = new Promise(function (resolve) {
setTimeout(function () {
console.log(1);
resolve("第一个promise");
}, 3000);
});
var p2 = new Promise(function (resolve) {
setTimeout(function () {
console.log(2);
resolve("第二个promise");
}, 1000);
});
Promise.race([p1, p2]).then(function (result) {
console.log(result);
});
// 2
// 第二个promise
// 1
应用场景1:图片请求超时
应用场景2:请求超时提示
描述:有些时候,我们前一秒刷着新闻,下一秒进入电梯后,手机页面上就会提示你 “网络不佳”
Promise.prototype.then
p.then(onFulfilled[, onRejected]);
p.then(value => {
// fulfillment
}, reason => {
// rejection
});
应用场景1:下个请求依赖上个请求的结果
描述:类似微信小程序的登录,首先需要 执行微信小程序的 登录 wx.login 返回了code,然后调用后端写的登录接口,传入 code ,然后返回 token ,然后每次的请求都必须携带 token,即下一次的请求依赖上一次请求返回的数据
应用场景2:中间件功能使用
描述:接口返回的数据量比较大,在一个then 里面处理 显得臃肿,多个渲染数据分别给个then,让其各司其职
async/await的作用与用法
async/await 是ES2017(ES8)提出的基于Promise的解决异步的最终方案。
async
async是一个加在函数前的修饰符,被async定义的函数会默认返回一个Promise对象resolve的值。因此对async函数可以直接then,返回值就是then方法传入的函数。
await
await也是一个修饰符,只能放在async定义的函数内。可以理解为等待。
await 修饰的如果是Promise对象:可以获取Promise中返回的内容(resolve或reject的参数),且取到值后语句才会往下执行;
如果不是Promise对象:把这个非promise的东西当做await表达式的结果。
async/await 的正确用法
// 使用async/await获取成功的结果
// 定义一个异步函数,3秒后才能获取到值(类似操作数据库)
function getSomeThing(){
return new Promise((resolve,reject)=>{
setTimeout(()=>{
resolve('获取成功')
},3000)
})
}
async function test(){
let a = await getSomeThing();
console.log(a)
}
test(); // 3秒后输出:获取成功