问:Promise是什么?
答:
- 抽象的说:就是异步编程的一种解决方案
- 具体的说:从语法上来说promise是一个构造函数、从功能上来说Promise 对象用来封装一个异步操作并可以获取其结果数据
补充:封装异步操作将来可以得到它最终异步执行的结果
而旧的(也就是之前的)异步编程是 纯回调 !
Promise 对象
引用MDN: https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Promise
语法:
new Promise( function(resolve, reject) {…} /* executor */ );
状态
一个 Promise有以下几种状态:
- pending: 初始状态,既不是成功,也不是失败状态,进行中。
- fulfilled: 意味着操作成功完成。
- rejected: 意味着操作失败。
1、pending 变为 fulfilled
2、pending 变为 rejected
说明:只有这2种,且一个promise对象只能改变一次,无论变为成功还是失败,都会有一个结果数据,成功的结果数据一般称为value,失败的结果数据一般称为reason。
只要处于 fulfilled 和 rejected ,状态就不会再变了即 resolved(已定型)。
基本流程:
注意:此图中存在一点问题,所以引用mdn图来修改
流程解析:
1、首先新建一个promise对象,开始状态为pending,指定传入参数为函数,在函数里面启动异步任务;2、一旦去new ,它内部去执行异步操作,而执行异步任务最终可能成功,也可能失败;
3、如果成功,执行resolve();如果失败了,执行 reject();
4、一旦成功 promise 就会变成 fulfilled 状态、失败的话promise 就变成 rejected 状态;
5、之后调用成功或者失败的回调函数,.then()指定成功的回调函数是onResolved(),注意:.then()成功和失败都可以指定;而.catch()只能指定失败的回调函数是onRejected();
6、.then()返回一个新的promise对象;
在过去,要想做多重的异步操作,会导致经典的回调地狱:
doSomething(function(result) {
doSomethingElse(result, function(newResult) {
doThirdThing(newResult, function(finalResult) {
console.log('Got the final result: ' + finalResult);
}, failureCallback);
}, failureCallback);
}, failureCallback);
现在,我们可以把回调绑定到返回的 Promise 上,形成一个 Promise 链:
doSomething().then(function(result) {
return doSomethingElse(result);
})
.then(function(newResult) {
return doThirdThing(newResult);
})
.then(function(finalResult) {
console.log('Got the final result: ' + finalResult);
})
.catch(failureCallback);
then里的参数是可选的,catch(failureCallback) 是 then(null, failureCallback) 的缩略形式。如下所示,我们也可以用箭头函数来表示:
doSomething()
.then(result => doSomethingElse(result))
.then(newResult => doThirdThing(newResult))
.then(finalResult => {
console.log(`Got the final result: ${finalResult}`);
})
.catch(failureCallback);
以上引用MDN:
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Guide/Using_promises
<script>
// 1、创建一个新的promise对象
// 开发中这里先用const,能不改就不改,后面发现这个变量需要改变,再改为let
const p = new Promise((resolve, reject) => { //执行器函数 executor
console.log('执行 excutor') //立刻执行
// 2、模拟执行异步操作任务
setTimeout(() => {
const nowTime = Date.now() //如果当前时间为偶数代表成功,否则代表失败
// 3.1、如果成功了,调用resolve(value) 成功数据成为value
if(nowTime %2 == 1){
resolve('成功的数据,time=' + nowTime)
} else {
// 3.2、如果失败了,调用reject(reason) 失败数据成为reason
reject('失败的数据,time=' + nowTime)
}
}, 1000)
})
console.log('new promise()之后') //先输出执行 excutor
// 接下来.then()来指定是否成功和失败的回调函数
p.then(
value => { //接收得到成功的value数据 onResolved
console.log('成功的回调', value)
},
reason => { //接收得到失败的reason数据 onRejected(看到on基本上是回调函数)
console.log('失败的回调', reason)
}
)
</script>
问:为什么使用 Promise ?
答:
-
指定回调函数的方式更加灵活;
旧的:必须在启动异步任务前指定
promise:启动异步任务 => 返回 promise 对象 => 给 promise 对象绑定回调函数(甚至可以在异步任务就结束后指定) -
支持链式调用,可以解决回调地狱问题;
什么是回调地狱?回调函数嵌套调用,外部回调函数异步执行的结果是嵌套的回调函数执行的条件
回调地狱的缺点:不便于阅读、不便于异常处理
如何解决,方案?promise 链式调用(但不是最优解决,因为还有回调函数,虽然没有嵌套)
最终解决方案: async 和 await
function successCallback(result) {
console.log('声音文件创建成功' + result)
}
function failureCallback(error) {
console.log('声音文件创建失败' + error)
}
// 解析 : 1. 指定回调函数的方式更加灵活;
/* 1.1 纯回调函数 */
//启动任务(audioSettings)前必须指定回调函数(callback)
createAudioFileAsync(audioSettings, successCallback, failureCallback)
// 这里解析一下:在执行异步操作之前就要指定后面的成功或失败的回调,交给它,其实还没有真正的执行异步操作
// 就是一上来先指定回调函数,后面才启动异步任务,必须是这样才可以,不能先启动异步任务,再指定回调函数,这样得不到数据
/* 1.2 promise */
//可在启动任务(audioSettings)后指定回调函数(callback)
const promise = createAudioFileAsync(audioSettings)
setTimeout(() => {
promise.then(successCallback, failureCallback)
}, 1000)
// 这里解析一下:异步任务已经启动,需要做一段时间,此时没有指定回调函数,在后面指定回调函数,算是在异步启动后面指定
// 这里是在异步启动之后,但是是在异步任务成功或者说有结果之前指定的异步函数;
// 能不能在有结果之后指定呢 ?
// 可以的 promise指定回调函数的时机可以是启动后,也可以启动任务前,甚至可以是任务完成结束后指定仍然可以得到数据
//试一下 promise指定回调函数的时机在任务完成结束后指定
const p = new Promise((resolve, reject) => { //执行器函数 executor
console.log('执行 excutor')
// 2、模拟执行异步操作任务
setTimeout(() => {
const nowTime = Date.now() //如果当前时间为偶数代表成功,否则代表失败
// 3.1、如果成功了,调用resolve(value) 成功数据成为value
if(nowTime %2 == 1){
resolve('成功的数据,time=' + nowTime)
} else {
// 3.2、如果失败了,调用reject(reason) 失败数据成为reason
reject('失败的数据,time=' + nowTime)
}
},1000)
})
console.log('new promise()之后')
setTimeout(() => {
p.then(
value => { //接收得到成功的value数据 onResolved
console.log('成功的回调', value)
},
reason => { //接收得到失败的reason数据 onRejected(看到on基本上是回调函数)
console.log('失败的回调', reason)
}
)
},3000)
//结果是可以的
// 解析 : 2. 支持链式调用,可以解决回调地狱问题;
/* 2.1 回调地狱 */
//回调函数的嵌套
doSomething(function (result) { //第一个函数function就是sucessCallback
doSomethingElse(result, function (newResult) {
doThirdThing(newResult, function (finalResult) {
console.log('Got the final result' + finalResult)
}, failureCallback)
}, failureCallback)
}, failureCallback)
// 回调地狱涉及多个异步操作,而且是多个连续串联执行的回调,如异步操作1、异步操作2、异步操作3、
// 而 异步操作2 是以 异步操作1 的结果为条件的,异步操作3 是以 异步操作2 的结果为条件的,同理一直往下
// 一个异步任务要执行一个函数,就是指定一个回调函数,回调函数接收成功的结果,但也会传失败的回调
// 说明第一个异步任务有结果了,准备执行第二个,以第一个成功的结果为条件,
// 再指定一个回调函数,成功的回调函数接收新的成功的结果,但也会传新的失败的回调,
// 依次往下,最后一个回调函数接收结果,这种情况称为回调地狱
/* 2.2 链式调用 */
doSomething().then(function(result) { //result是doSomething函数成功执行的返回值
return doSomethingElse(result) //执行器函数,同步回调
})
.then(function(newResult){ //newResult是doSomethingElse成功执行的返回值
return doThirdThing(newResult)
})
.then(function(finalResult){
console.log('Got the final result' + finalResult)
})
.catch(failureCallback) //统一的错误处理 异常传透
// 封装一个函数 ,返回一个promise对象,一旦得到一个promise对象说明启动了异步任务(或一个promise对应一个异步任务)
// .then()成功的回调,return 返回一个调用第二个异步处理的新的promise对象 doSomethingElse(result)
// .then(function(newResult) //newResult是doSomethingElse成功执行的返回值
// 处理异常:.catch(failureCallback) 统一的错误处理
// 中间只需要写成功的回调,不用写失败的回调,但是他们中任意一个异常都会找.catch ————异常传透
// 优点:1、编码从上往下写,阅读方便 2、异常处理更加方便
/* 2.3 async/await : 回调地狱的终极解决方案 */
//根本上去掉回调函数
async function request() {
try{
const result = await doSomething()
const newResult = await doSomethingElse(result)
const finalResult = await doThirdThing(newResult)
console.log('Got the final result' + finalResult)
} catch (error) {
failureCallback(error)
}
}
// 发现promise里面还有回调函数,虽然没有回调嵌套了;
// 用 async/await 解决回调函数,从源码和程序感受看没有回调函数,但从编译后代码的看是有回调函数(先不关注源码);
// 从编码上,同步的编码方式(没有回调函数的),而promise不能称为纯粹的同步的编码方式;
// await doSomething() await之后得到结果,然后执行第二个事情 doSomethingElse(result) 第二个事情必须是第一个事情成功之后再去处理,
// 但是中间可能出异常,用try catch 去处理
使用Promise
API:一个意义是语法文档,另一个意义是前后端交互的接口,发请求需要API接口
// .all 是promise函数对象的方法
// .then() 是实例对象调用,是原型对象的方法
// .catch() 是实例对象调用,是原型对象的方法
// promise 函数对象就一个,实例对象可多个,一般我们用实例对象,只有产生实例对象才能启动异步操作
/*
1、Promise 构造函数:Promise (excutor){}
excutor函数:同步执行 (resolve,reject) => {}
resolve函数:内部定义成功时调用的函数 value => {}
reject函数 :内部定义失败时调用的函数 reason => {}
说明:excutor 会在Promise内部立即同步调用,异步操作在执行器中执行
2、Promise.prototype.then 方法 :(onResolve,onReject) => {}
onResolve函数 :成功的回调函数 (value) => {}
onReject函数 :失败的回调函数 (reason) => {}
说明:指定用于得到成功value的成功回调和用于得到失败reason的失败回调
返回一个新的promise对象
3、Promise.prototype.catch 方法:(onRejected) => {}
onReject函数:失败的回调函数 (value) => {}
说明:then()的语法糖,相当于:then(undefined,onRejected)
4、Promise.resolve方法:(value) => {}
value:成功的数据或promise对象
说明:返回一个成功/失败的promise对象
5、Promise.reject方法:(reason) => {}
reason:失败的原因
说明:返回一个失败的promise对象
6、Promise.all方法:(promises) => {}
promise:包含n个promise的数组
说明:返回一个新的promise对象,只有所有的promise都成功才成功,只要有一个失败了就直接失败
7、Promise.race方法:(promsies) => {}
promise:包含 n个promise的数组
说明:返回一个新的promise。第一个完成的promise的结果状态就是最终的结果状态
*/
new Promise((resolve, reject) => {
setTimeout(() => {
resolve('成功的数据') //只能执行一个,因为只能改变一个状态
//reject('失败的数据')
}, 1000);
}).then(
// .then执行成功或者失败的回调函数
value => {
console.log('onResolved()1', value)
}
).catch(
// 接收失败的回调
reason => {
console.log('onRejected()1', reason)
}
)
当把那个resolve换为reject的话
//resolve('成功的数据') //只能执行一个,因为只能改变一个状态
reject('失败的数据')
// 案例:
// 要求:产生一个成功值为1的promise对象
const p1 = new Promise((resolve, reason) => {
resolve(1)
})
// 此时p1就是一个成功值为1的promise对象
// 要求:用resolve产生一个成功值为2的promise对象
const p2 = Promise.resolve(2) //语法糖
// 要求:产生一个失败的值为3的promise对象
const p3 = Promise.reject(3)
// 得到结果
p1.then(value => {console.log(value)})
p2.then(value => {console.log(value)})
p3.catch(reason => {console.log(reason)}) //或者 p3.then(null, reason => {console.log(reason)})
const pAll = Promise.all([p1, p2, p3])
// 因为这里p3是失败的的promise对象
// Promise.all方法 ,只有所有的promise都成功才成功,只要有一个失败了就直接失败
// const pAll = Promise.all([p1, p2])
pAll.then(
value => {
console.log('all onResolved()',value)
},
reason => {
console.log('all onRejected()', reason)
}
)
去掉p3的promise对象的结果:
// const pAll = Promise.all([p1, p2, p3])
// 因为这里p3是失败的的promise对象
// Promise.all方法 ,只有所有的promise都成功才成功,只要有一个失败了就直接失败
const pAll = Promise.all([p1, p2])
// 去掉p3 都是成功的 promise对象
pAll.then(
values => {
console.log('all onResolved()',values)
},
reason => {
console.log('all onRejected()', reason)
}
)
//values里面的顺序和数组 [p1, p2] 里面的一致,和执行完成顺序无关
const pRace = Promise.race([ p1, p2, p3])
// 结果状态有谁决定?由第一个完成的决定,后面不管
//p1是成功的,所以结果状态为成功
pRace.then(
value => {
console.log('race onResolved()', value)
},
reason => {
console.log('race onRejected()', reason)
}
)
const pRace = Promise.race([ p3, p1, p2])
// 结果状态有谁决定?由第一个完成的决定,后面不管
//p3是失败的,所以结果状态为失败
pRace.then(
value => {
console.log('race onResolved()', value)
},
reason => {
console.log('race onRejected()', reason)
}
)
const pRace = Promise.race([ p1, p2, p3])
// 结果状态有谁决定?由第一个完成的决定,后面不管
// const pRace = Promise.race([ p3,p1, p2])
pRace.then(
value => {
console.log('race onResolved()', value)
},
reason => {
console.log('race onRejected()', reason)
}
)
// 但是注意,此时如果之前的p1改下,加个延时, 如下
// const p1 = new Promise((resolve, reason) => {
// setTimeout(() => {
// resolve(1)
// }, 100);
// })
如果是:
// const pRace = Promise.race([ p1, p2, p3])
// 结果状态有谁决定?由第一个完成的决定,后面不管
const pRace = Promise.race([ p3, p1, p2])
pRace.then(
value => {
console.log('race onResolved()', value)
},
reason => {
console.log('race onRejected()', reason)
}
)
// 但是注意,此时如果把p1加个延时,如下
// const p1 = new Promise((resolve, reason) => {
// setTimeout(() => {
// resolve(1)
// }, 100);
// })
//注意:所以说并不是第一个的 决定
接下来说说 Promise 的几个重要问题点:
1、如何改变Promise的状态?
2、一个Promise指定多个成功/失败回调函数,都会调用吗?
/*
1、如何改变promise的状态?
1、resolve(value):如果当前是pending 就会变成resolved
2、reject(reason):如果当前是pending 就会变成rejected
3、抛出异常:如果当前是pending就会变成rejected
2、一个promise指定多个成功/失败回调函数,都会调用吗?
当promise改变为对应状态是都会调用
*/
const p = new Promise((resolve, reject) => {
// resolve(1) //promise变为resolved成功的状态
// reject(2) //promise变为rejected失败的状态
// throw new Error('出错了') //抛出异常,promise变为rejected失败状态,reason值为 抛出的error
throw 3 //抛可以,抛出任意值都可以 、抛出异常,promise变为rejected失败状态,reason值为 抛出的3
})
// console.log(p)
// 失败没处理
p.then(
value => {},
reason => { console.log('reason', reason)}
)
p.then(
value => {},
reason => { console.log('reason2', reason)}
)
3、改变Promise状态和指定回调函数谁先谁后?
/*
3、改变promise状态和指定回调函数谁先谁后?
1、都有可能,正常情况下是先指定回调再改变状态,但可以先改状态再指定回调
2、如何先改变状态再指定回调
1、在执行器中直接使用resolve() / reject()
2、延迟更长的时间才调用 then()
3、什么时候才能得到数据?
1、如果先指定的回调,那当状态发生改变时,回调函数就会调用,得到数据
2、如果先改变的状态,那当指定回调时,回调函数就会调用,得到的数据
*/
// 常规的顺序:先指定回调函数 ,后改状态
new Promise((resolve, reject) => {
setTimeout(() => {
resolve(1) //后改状态 (同时指定数据),异步执行回调函数
}, 1000);
}).then( //先指定回调函数,保存指定的回调函数
value => {console.log('value1', value)},
reason => {console.log('reason1', reason)}
)
//如何先改状态后指定回调函数?
// 第一种:
new Promise((resolve, reject) => {
resolve(2) //先改状态 (同时指定数据)
}).then( //后指定回调函数,保存指定的回调函数
value => {console.log('value2', value)},
reason => {console.log('reason2', reason)}
)
console.log('----') //判断是异步还是同步的,随便打印一下,如果 value是异步,会放队列中
// 第二种:
const p = new Promise((resolve, reject) => {
setTimeout(() => {
resolve(3) //先改状态 (同时指定数据),异步执行回调函数
}, 1000);
})
setTimeout(() => {
p.then( //后指定回调函数,保存指定的回调函数
value => {console.log('value3', value)},
reason => {console.log('reason3', reason)}
)
}, 1100);
console.log(’----’) //判断是异步还是同步的,随便打印一下,如果 value是异步,会放队列中
promise无论是成功还是失败的回调函数,都是异步执行的,即使条件已经满足了,也不是马上执行
4、Promise.then()返回新的promise的结果状态有什么决定?
/*
4、promise.then()返回新的promise的结果状态有什么决定?
1、简单表述:由then()指定的回调函数执行的结果决定
2、详细表述:
1、如果抛出异常,新的promise变为 rejected,reason 为抛出的异常
2、如果返回的非 promise 的任意值,新promise变为resolved ,value为返回的值
3、如果返回的是另一个新的promise,此promise的结果就会成为新promise的结果
*/
new Promise((resolve, reject) => {
resolve(1)
}).then(
value => {
console.log('onResolved1()', value) // 结果: onResolved1() 1
},
reason => {
console.log('onRejected1()', reason)
}
).then(
value => {
console.log('onResolved2()', value) // 结果: onResolved1() undefined
},
reason => {
console.log('onRejected2()', reason)
}
)
new Promise((resolve, reject) => {
// resolve(1)
reject(1)
}).then(
value => {
console.log('onResolved1()', value)
},
reason => {
console.log('onRejected1()', reason)
}
).then(
value => {
console.log('onResolved2()', value)
},
reason => {
console.log('onRejected2()', reason)
}
)
new Promise((resolve, reject) => {
resolve(1)
// reject(1)
}).then(
value => {
console.log('onResolved1()', value)
return 2
// return Promise.resolve(3)
// return Promise.reject(4)
},
reason => {
console.log('onRejected1()', reason)
}
).then(
value => {
console.log('onResolved2()', value)
},
reason => {
console.log('onRejected2()', reason)
}
)
5、promise如何串联多个操作任务?
/*
5、promise如何串联多个操作任务?
1、promise的then()返回一个新的promise,可以开成then()的链式调用
2、通过then的链式调用串连多个同步 / 异步任务
*/
new Promise((resolve, reject) => {
setTimeout(() => {
console.log('执行任务1(异步)')
resolve(1)
}, 1000);
}).then(
value => {
console.log('任务1的结果:', value)
console.log('执行任务2(同步)')
return 2 //同步操作直接return 就行,但是异步就不能直接 return 需要return new promise
}
).then(
value => {
console.log('任务2的结果:', value)
return new Promise((resolve, reject) => { //异步就不能直接 return 3 需要return new Promise
//启动异步任务3(异步)
setTimeout(() => {
console.log('执行任务3(异步)')
resolve(3)
}, 1000);
}).then()
}
).then(
value => {
console.log('任务3的结果:', value)
}
)
6、promise异常传 (穿) 透?
/*
6、promise异常传透?
1、当使用promise的then链式调用时,可以在最后指定失败的回调
2、前面任何操作出了问题,都会传到最后失败的回调函数中处理
*/
new Promise((resolve, reject) => {
// resolve(1)
reject(1)
}).then(
value => {
console.log('onResolved1()', value)
return 2
}
// 通过.then找他失败的回调函数,但是发现没写,
// 相当于写了:reason => {throw reason};
// 注意:它不是一下子找到.catch,而是一步一步
// 串过去;
).then(
value => {
console.log('onResolved2()', value)
return 3
}
).then(
value => {
console.log('onResolved3()', value)
}
).catch(reason => {
console.log('onRejected1()', reason)
})
通过.then找它失败的回调函数,但是发现没写,相当于写了:reason => { throw reason };
注意:它不是一下子找到.catch,而是一步一步串过去;(对比上面的代码)
写和不写都可以,但是不写是 默认写法,内部去处理的
new Promise((resolve, reject) => {
// resolve(1)
reject(1)
}).then(
value => {
console.log('onResolved1()', value)
return 2
},
reason => {throw reason}
// 通过.then找他失败的回调函数,但是发现没写,
// 相当于写了:reason => {throw reason};
// 注意:它不是一下子找到.catch,而是一步一步
// 串过去;
).then(
value => {
console.log('onResolved2()', value)
return 3
},
reason => {throw reason}
).then(
value => {
console.log('onResolved3()', value)
},
//reason => {throw reason}
reason => Promise.reject(reason)
// 这里不一定非要写throw,.then返回的promise有两种情况会失败,一种返回一个失败的promise,一种抛异常
// 其他.then返回的promise成功有两种情况,一种成功的promise,一种返回其他的值
).catch(reason => {
console.log('onRejected1()', reason)
})
7、中断promise链?
/*
7、中断promise链?
1、当使用promise的then链式调用时,在中间中断,不在调用后面的回调函数
2、办法:在回调函数中返回一个pending状态的promise对象
*/
new Promise((resolve, reject) => {
// resolve(1)
reject(1)
}).then(
value => {
console.log('onResolved1()', value)
return 2
},
reason => {throw reason}
// 通过.then找他失败的回调函数,但是发现没写,
// 相当于写了:reason => {throw reason};
// 注意:它不是一下子找到.catch,而是一步一步
// 串过去;
).then(
value => {
console.log('onResolved2()', value)
return 3
},
reason => {throw reason}
).then(
value => {
console.log('onResolved3()', value)
},
reason => Promise.reject(reason)
//这里不一定要写throw,.then返回的promise两种情况会失败,一种返回一个失败的promise,一种抛异常
// 其他.then返回的promise成功有两种情况,一种成功的promise,一种返回其他的值
).catch(reason => {
console.log('onRejected1()', reason)
// throw reason
// return Promise.reject(reason)
return new Promise(() => {}) //返回一个pending的promise 中断 promise 链
}).then(
value => {
console.log('onResolved4()', value)
return 3
},
reason => {
console.log('onRejected4()', reason)
}
)
后面再说说Promise的自定义吧
推荐博客:小渣亮的promise的自定义