用 Promise 主要是换种写法而已 (把异步编程变成同步的一种写法) 英文是‘’承诺‘’的意思,表示其它手段无法改变。
三种状态::pending(进行中)、fulfilled(已成功)和rejected(已失败),只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态,这也是Promise这个名字的由来。
Promise对象的状态改变,只有两种可能:从pending变为fulfilled和从pending变为rejected,只要这两种情况发生,状态就凝固了,不会再变了,会一直保持这个结果,这时就称为 resolved(已定型),如果改变已经发生了,你再对Promise对象添加回调函数,也会立即得到这个结果,这与事件(Event)完全不同,事件的特点是,如果你错过了它,再去监听,是得不到结果的。
注意:只有 Promise 体内的代码是同步进行的。 Promise:(低版本用不了)
pending :进行中
resolve : 决定(成功)
reject :拒绝(失败)
finally :成功和失败都会执行(非Promise自带,需自行扩展)
done:会捕捉到任何可能出现的错误,并向全局抛出。(非Promise自带,按心情自行扩展)
一. new Promise((resolve,reject)=>{ resolve( ); reject( ) })
1 let str = 10; 2 //p是 <promise> 对象 可以进行链式调用,链式调用的返回值是 this (这里指向p,所以可以一直使用 .then( ) 的方法) 3 let p = new Promise(function (resolve, reject) { 4 setTimeout(function () { 5 str = 20; 6 resolve(str) 7 }, 100); 8 }); 9 10 p.then((str) => { //这里拿到的 str 是 new 里面的 resolve(str) 里的 str 相当于这里的函数,在上面被调用,并传实参 str 11 console.log(str) //20 12 return 5; // 虽然写了 return,但返回的依然是一个新的 promise 对象,而这里的 return 是给下一个.then 传参。 13 }).then((a) => { 14 console.log(a) //5 15 }); 16 17 p.then((xxx) => { //这里拿到的 xxx 是 new 里面的 resolve(str) 里的 str 相当于这里的函数,在上面被调用,并传实参 xxx 18 console.log(xxx) //20 19 })
// 先打印 11行的 20, 再输出 18行的 20, 最后输出 14行的 5, 因为 11行 和 18行, 都是最先被执行的 promise, 而14行是一个新的 promise,属于新的微任务,进入异步队列
二. 哪里可以拿到 reject 中的内容
reject后的东西,一定会进入then中的第二个回调,如果then中没有写第二个回调,则进入catch,没有 then 也会进入 catch
var p1=new Promise((resolve,rej) => { console.log('没有resolve') //throw new Error('手动返回错误') rej('失败了') }) p1.then(data =>{ console.log('data::',data); },err=> { console.log('err::',err) }).catch( res => { console.log('catch data::', res) })
then中没有第二个回调的情况
var p1=new Promise((resolve,rej) => { console.log('没有resolve') //throw new Error('手动返回错误') rej('失败了') }) p1.then(data =>{ console.log('data::',data); }).catch( res => { console.log('catch data::', res) }) ———————结果——————— 没有resolve catch data:: 失败了
同理: resolve的东西,一定会进入then的第一个回调,肯定不会进入catch
三. Promise.all ( [ p1 , p2 , ...... ] )
Promise.all可以将多个Promise实例包装成一个新的Promise实例。同时,成功和失败的返回值是不同的,成功的时候返回的是一个结果数组,而失败的时候则返回最先被reject失败状态的值。(all会将传入的数组中的所有promise全部决议以后,将决议值以数组的形式传入到观察回调中,任何一个promise决议为拒绝,那么就会调用拒绝回调。)
let p1 = new Promise((resolve, reject) => { resolve('成功了') }) let p2 = new Promise((resolve, reject) => { resolve('success') }) let p3 = Promse.reject('失败') Promise.all([p1, p2]).then((result) => { console.log(result) //['成功了', 'success'] }).catch((error) => { console.log(error) }) Promise.all([p1,p3,p2]).then((result) => { console.log(result) }).catch((error) => { console.log(error) // 失败了,打出 '失败' })
Promse.all在处理多个异步处理时非常有用,比如说一个页面上需要等两个或多个ajax的数据回来以后才正常显示,在此之前只显示loading图标。
代码模拟:
let wake = (time) => { return new Promise((resolve, reject) => { setTimeout(() => { resolve(`${time / 1000}秒后醒来`) }, time) }) } let p1 = wake(3000) let p2 = wake(2000) Promise.all([p1, p2]).then((result) => { console.log(result) // [ '3秒后醒来', '2秒后醒来' ] }).catch((error) => { console.log(error) })
四. Promise.race的使用
let p1 = new Promise((resolve, reject) => { setTimeout(() => { resolve('success') },1000) }) let p2 = new Promise((resolve, reject) => { setTimeout(() => { reject('failed') }, 500) }) Promise.race([p1, p2]).then((result) => { console.log(result) }).catch((error) => { console.log(error) // 打开的是 'failed' })
五. Promise是如何捕获异常的?与传统的try/catch相比有什么优势?
传统的 try / catch 捕获异常方式是无法捕获异步的异常的。
而对于 Promise 对象来说,构造 Promise 实例时的代码如果出错,则会被认为是一个拒绝的决议,并会向观察回调中传递异常信息。所以即使是一个异步的请求,Promise 也是可以捕获异常的。此外,Promise 还可以通过 catch 回调来捕获回调中的异常。
六.如果向Promise.all()和Promise.race()传递空数组,运行结果会有什么不同?
all会立即决议,决议结果是fullfilled,值是undefined
race会永远都不决议,程序卡住……
七.扩展一个 Promise.finally()
扩展一个 Promise.finally(),finally方法用于指定不管Promise对象最后状态如何,都会执行的操作.
它与done方法的最大区别在于,它接受一个普通的回调函数作为参数,该函数不管怎样都必须执行。
//添加finally方法 Promise.prototype.finally=function (callback) { var p=this.constructor; return this.then(//只要是promise对象就可以调用then方法 value => p.resolve(callback()).then(() => value), reason => p.resolve(callback()).then(() => {throw reason}) ); }
对finally方法的理解:
(1) p.resolve(callback())
这句函数callback已经执行
(2) finally方法return的是一个promise对象,所以还可以继续链式调用其他方法
(3) 对于Promise.resolve方法: Promise.resolve('foo') 等价于 new Promise(resolve => resolve('foo'));
所以可以通过then方法的回调函数 接受 实例对象返回的参数
比如: Promise.resolve(function(){console.log(2);}).then(function(cb){cb()})
(4) p.resolve(callback()).then(() => value)
调用then的目的是给promise实例即this添加成功和失败的回调函数
Promise.finally() 的使用方法:
Promise.finally(),按照以上的扩展方式
八. 扩展一个 Promise.done()
Promise
对象的回调链,不管以then
方法或catch
方法结尾,要是最后一个方法抛出错误,都有可能无法捕捉到(因为 Promise
内部的错误不会冒泡到全局)。因此,我们可以提供一个done
方法,总是处于回调链的尾端,保证抛出任何可能出现的错误。Promise.prototype.done = function (onFulfilled, onRejected) { this .then(onFulfilled, onRejected) .catch(function (reason) { // 抛出一个全局错误 setTimeout(() => { throw reason }, 0) }) }
done
方法的使用,可以像then
方法那样用,提供fulfilled
和rejected
状态的回调函数,也可以不提供任何参数。但不管怎样,done
都会捕捉到任何可能出现的错误,并向全局抛出。执行代码的先后顺序:
先 主任务 后 任务队列。
任务队列又分:宏任务(task)、微任务(Microtasks)
微任务(Microtasks)-> Promise 、process.nextTick
宏任务(task)-> 定时器(比如:setTimeout)、事件(比如:onclick)、整体代码script
任务队列既有宏任务又有微任务,先执行微任务,再执行宏任务。(1.主任务(主线程)-> 2. 微任务 -> 3.宏任务)。这种循环机制叫做任务循环(event loop)
基于 Promise 之后。又有 Generator ,和它的语法糖:async
Generator:函数是协程在ES6的实现,最大的特点就是可以交出函数的执行权(即暂停执行),交给 yield 后面的 语句,等此语句执行完,再执行下面的代码。
协程:
第一步:协程 A
开始执行。
第二步:协程 A
执行到一半,进入暂停,执行权转移到协程 B
。
第三步:(一段时间后)协程 B 交还执行权
第四步:协程 A 恢复执行。
也就是说 Promise 和 Generator 一个明显不同的特点是,前者只在Promise体内进行异步代码的同步执行,(不会阻断主任务)
而后者是阻断(主任务)的执行,等到异步的代码执行完毕之后再执行被阻断的(主任务)代码。
function* gen(x){ //这个函数体内任何地方console.log(y)都是undefined var y = yield x + 2 ; var z = yield x + y ; return y; }; let y = gen(5); console.log(y.next ()) // 让代码继续执行,并 返回值 拿到 return 后面的 y -> {value: 7, done: false} y.next(y.next ( ).value) //next中可以传入数据(传入的数据为上一次yield表达式的返回值(就是这里的y)),为了下一步操作的运算。
async:(**现在最常用)
function fn2(){ return new Promise(function(resolve){ setTimeout(function(){ resolve(50); },1000); }) } async function fn(){ let y = await fn2() + 2; console.log(y); } fn();//调用后,输出 52 。要 等待 fn2执行完(将异步转成同步的感觉)
主线程-微任务-宏任务 练习:(如何使用async、await、Promise以及各种任务的执行特点)
async function fn() { console.log(1); let b = await fn2();//b拿到return的值 = resolve()里面的值,并阻塞下面的代码。 console.log(b); console.log(2)//出现在await之后,也就是2一定跟着3之后出现 }; function fn2() { return new Promise((resolve, reject) => {//async、await要配合Promise使用。 console.log(9); setTimeout(() => { resolve(3)//进入宏任务,定时器谁先触发谁先执行。 }, 1000) }); } // async function fn() { // //function fn() { // console.log(1); // let b = await fn2(); // console.log(2)//进入微任务,谁先进入谁先触发。 // }; // function fn2() { // console.log(9); // setTimeout(() => { // resolve(3)//进入宏任务,定时器谁先触发谁先执行。 // }, 1000) // }; setTimeout(() => { console.log(6)//进入宏任务 }, 500); fn() let a = new Promise((resolve, reject) => { console.log(4); resolve() }) a.then(() => { console.log(5)//进入微任务 }); //fn() console.log(8); //1,9,4,8,2,5,3,6 //4,1,9,8,5,2,3,6 //注:如果await后面的函数,没有被Promise包着。 await 下面的代码会进微任务队列(谁先进入谁先执行),定时器会进宏任务队列(谁先触发谁先执行)。 //1,9,4,8,5,6,3,2 //await后面的函数被Promise包着,那么await后面的函数才会阻塞下面的函数,等await后面的函数执行完,再往下执行(只在函数体内发生)。