Promise
layout: post
title: Promise
tags: javascript Promise
categories:Ajax Promise
1.Promise 的基本概念 ES6 将其写进了语言标准里统一了用法,是一个构造函数,用来生成 Promise 实例参数为一个执行器函数 (执行器函数是立即执行的), 该函数有两个函数作为参数,第一个参数是成功时的回调,第二个参数是失败时的回调函数的方法有 resolve (可以处理成功和失败)、reject (只处理失败)、all 等方法then、catch、finally 方法为 Promise 实例上的方法
-
模拟一个promise的get请求 ``` let count = 0 function customGet(url){ count += 1 return new Promise((resolve, reject)=>{ const xmlHttp = new XMLHttpRequest(); xmlHttp.open(“GET”,url, true); xmlHttp.onload = ()=>{ console.log(xmlHttp, ‘xmlHttp—onload’); if (xmlHttp.readyState === 4 && xmlHttp.status === 200) { console.log(‘customGet请求成功了’); // 返回非Promise,结果为成功状态 resolve({data:
第${count}次请求获取数据成功
})// 返回Promise,结果由Promise决定 // resolve(Promise.reject('resolve中返回reject')) } else { reject('customGet请求错误了') } } // Promise状态改变就不会再变 // onreadystatechange方法会被执行四次 // 当地次进来的时候,readyState不等于4,执行else逻辑,执行reject,状态变为Rejected,所以即使再执行if,状态之后不会再改变 // xmlHttp.onreadystatechange = function(){ // console.log(xmlHttp,'xmlHttp---onreadystatechange') // if (xmlHttp.readyState === 4 && xmlHttp.status === 200) { // console.log('customGet请求成功了'); // resolve({data:`第${count}次请求获取数据成功`}) // } else { // reject('customGet请求错误了') // } // } xmlHttp.send(); }) }
// 使用Promise,并且进行链式调用 customGet(‘https://v0.yiketianqi.com/api/cityall?appid=&appsecret=’).then((res)=>{ console.log(res.data); return ‘第一次请求处理后的数据’ }).then((data)=>{ console.log(data) // console.log(data.toFixed()); return customGet(‘https://v0.yiketianqi.com/api/cityall?appid=&appsecret=’) }).then((res)=>{ console.log(res.data); }).catch((err)=>{ // 以类似’冒泡’的性质再外层捕获所有的错误 console.error(err, ‘这是catch里的错误信息’); })
3. 手写实现简单的 Promise通过上面的回顾,我们已经了解了 Promise 的关键属性和特点,下面我们一起来实现一个简单的 Promise 吧 // 1、封装一个Promise构造函数,有一个函数参数
function Promise(executor){
// 7、添加对象属性PromiseState PromiseResult
this.PromiseState = 'pending'
this.PromiseResult = null
// 14、创建一个保存成功失败回调函数的属性
this.callback = null
// 8、this指向问题
const that = this
// 4、executor有两个函数参数(resolve,reject)
function resolve(data){
// 10、Promise状态只能修改一次(同时记得处理reject中的状态)
if(that.PromiseState !== 'pending') return
// console.log(this, 'this');
// 5、修改对象的状态PromiseState
that.PromiseState = 'Fulfilled'
// 6、修改对象的结果PromiseResult
that.PromiseResult = data
// 15、异步执行then里的回调函数
if(that.callback?.onResolve){
that.callback.onResolve(that.PromiseResult)
}
}
function reject(data){
console.log(that.PromiseState, 'that.PromiseState');
if(that.PromiseState !== 'pending') return
// 9、处理失败函数状态
that.PromiseState = 'Rejected'
that.PromiseResult = data
console.log(that.PromiseResult, 'that.PromiseResult');
console.log(that.PromiseState, 'that.PromiseState');
// 16、异步执行then里的回调函数
if(that.callback?.onReject){
that.callback.onReject(that.PromiseResult)
}
}
// 3、执行器函数是同步调用的,并且有两个函数参数
executor(resolve,reject)
}
// 2、函数的实例上有方法then
Promise.prototype.then = function(onResolve,onReject){
// 20、处理onReject没有的情况
if(typeof onReject !== 'function'){
onReject = reason => {
throw reason
}
}
// 21、处理onResolve没有的情况
if(typeof onResolve !== 'function'){
onResolve = value => value
}
// 17、每一个then方法都返回一个新的Promise,并且把上一个then返回的结果传递出去
return new Promise((nextResolve,nextReject)=>{
// 11、处理成功或失败
if(this.PromiseState === 'Fulfilled'){
// 12、将结果传递给函数
// onResolve(this.PromiseResult)
// 18、拿到上一次执行完后返回的结果,判断是不是Promise
const result = onResolve(this.PromiseResult)
if(result instanceof Promise){
result.then((v)=>{
nextResolve(v)
},(r)=>{
nextReject(r)
})
} else {
nextResolve(result)
}
}
// 当你一步步写下来的时候有没有怀疑过为什么不用else
if(this.PromiseState === 'Rejected'){
// 第12步同时处理此逻辑
// onReject(this.PromiseResult)
// 22、处理catch异常穿透捕获错误
try {
const result = onReject(this.PromiseResult)
if(result instanceof Promise){
result.then((v)=>{
nextResolve(v)
}).catch((r)=>{
nextReject(r)
})
} else {
nextReject(result)
}
} catch (error) {
nextReject(this.PromiseResult)
}
}
// 13、异步任务时处理成功或失败,想办法等异步任务执行完成后才去执行这两个函数
if(this.PromiseState === 'pending'){
this.callback = {
onResolve,
onReject
}
console.log(this.callback, 'this.callback');
}
})
}
// 19、函数实例上有方法catch
Promise.prototype.catch = function(onReject) {
return this.then(null,onReject)
}
// 使用自定义封装的Promise
const customP = new Promise((resolve,reject)=>{
// 模拟异步执行请求
// const xmlHttp = new XMLHttpRequest();
// xmlHttp.open("GET",'https://v0.yiketianqi.com/api/cityall?appid=&appsecret=', true);
// xmlHttp.onload = ()=>{
// if (xmlHttp.readyState === 4 && xmlHttp.status === 200) {
// resolve('success')
// } else {
// reject('error')
// }
// }
// xmlHttp.send();
// 同步执行
resolve('success')
// reject('error')
})
console.log(customP, 'customP');
customP.then((res)=>{
console.log(res, 'resolve回调');
return '第一次回调'
// return new Promise((resolve,reject)=>{
// reject('错错错')
// })
},(err)=>{
console.error(err, 'reject回调');
return '2121'
}).then(()=>{
console.log('then里面输出');
}).then().catch((err)=>{
console.error(err, 'catch里的错误');
})
3.// 在此函数内, 遇到await会暂停代码往下, 但不影响外面继续执行同步流程
// 等所有主线程同步代码走完, 再执行await并继续向下
console.log(1);
async function myFn() {
console.log(2);
const res = await 3
console.log(res);
console.log(4);
}
console.log(5);
myFn()
console.log(6);
4. await不能捕获失败结果, 需要使用try+catch关键字捕获
/*
try和catch语法
try {
// 这里放可能在执行中报错的代码
// 如果报错会终止代码继续执行, 直接跳转进catch里执行
} catch (err) {
// err接收try大括号内报错抛出的异常代码
}
*/
let p = new Promise((resolve, reject) => {
setTimeout(() => {
// resolve(1)
reject(new Error('失败'))
}, 2000)
})
async function myFn() {
try {
const res = await p
console.log(res);
} catch (err) {
console.error(err)
}
}
myFn()
5.
1.v1.0 初始版本myPromise function myPromise(constructor){ let self=this; self.status=”pending” //定义状态改变前的初始状态 self.value=undefined;//定义状态为resolved的时候的状态 self.reason=undefined;//定义状态为rejected的时候的状态 function resolve(value){ //两个===”pending”,保证了状态的改变是不可逆的 if(self.status===”pending”){ self.value=value; self.status=”resolved”; } } function reject(reason){ //两个===”pending”,保证了状态的改变是不可逆的 if(self.status===”pending”){ self.reason=reason; self.status=”rejected”; } } //捕获构造异常 try{ constructor(resolve,reject); }catch(e){ reject(e); } } 同时,需要在myPromise的原型上定义链式调用的then方法:
myPromise.prototype.then=function(onFullfilled,onRejected){
let self=this;
switch(self.status){
case “resolved”:
onFullfilled(self.value);
break;
case “rejected”:
onRejected(self.reason);
break;
default:
}
}
上述就是一个初始版本的myPromise,在myPromise里发生状态改变,然后在相应的then方法里面根据不同的状态可以执行不同的操作。
var p=new myPromise(function(resolve,reject){resolve(1)}); p.then(function(x){console.log(x)}) //输出1 但是这里myPromise无法处理异步的resolve.比如:
var p=new myPromise(function(resolve,reject){setTimeout(function(){resolve(1)},1000)});
p.then(function(x){console.log(x)}) //无输出 ```
6.v2.0基于观察模式实现 为了处理异步resolve,我们修改myPromise的定义,用2个数组onFullfilledArray和onRejectedArray来保存异步的方法。在状态发生改变时,一次遍历执行数组中的方法。
function myPromise(constructor){ let self=this; self.status=”pending” //定义状态改变前的初始状态 self.value=undefined;//定义状态为resolved的时候的状态 self.reason=undefined;//定义状态为rejected的时候的状态 self.onFullfilledArray=[]; self.onRejectedArray=[]; function resolve(value){ if(self.status===”pending”){ self.value=value; self.status=”resolved”; self.onFullfilledArray.forEach(function(f){ f(self.value); //如果状态从pending变为resolved, //那么就遍历执行里面的异步方法 });
}
}
function reject(reason){
if(self.status==="pending"){
self.reason=reason;
self.status="rejected";
self.onRejectedArray.forEach(function(f){
f(self.reason);
//如果状态从pending变为rejected,
//那么就遍历执行里面的异步方法
})
}
}
//捕获构造异常
try{
constructor(resolve,reject);
}catch(e){
reject(e);
} } 对于then方法,状态为pending时,往数组里面添加方法:
myPromise.prototype.then=function(onFullfilled,onRejected){
let self=this;
switch(self.status){
case “pending”:
self.onFullfilledArray.push(function(){
onFullfilled(self.value)
});
self.onRejectedArray.push(function(){
onRejected(self.reason)
});
case “resolved”:
onFullfilled(self.value);
break;
case “rejected”:
onRejected(self.reason);
break;
default:
}
}
这样,通过两个数组,在状态发生改变之后再开始执行,这样可以处理异步resolve无法调用的问题。这个版本的myPromise就能处理所有的异步,那么这样做就完整了吗?
没有,我们做Promise/A+规范的最大的特点就是链式调用,也就是说then方法返回的应该是一个promise。
3.v3.0then方法实现链式调用 要通过then方法实现链式调用,那么也就是说then方法每次调用需要返回一个primise,同时在返回promise的构造体里面,增加错误处理部分,我们来改造then方法
myPromise.prototype.then=function(onFullfilled,onRejected){
let self=this;
let promise2;
switch(self.status){
case “pending”:
promise2=new myPromise(function(resolve,reject){
self.onFullfilledArray.push(function(){
try{
let temple=onFullfilled(self.value);
resolve(temple)
}catch(e){
reject(e) //error catch
}
});
self.onRejectedArray.push(function(){
try{
let temple=onRejected(self.reason);
reject(temple)
}catch(e){
reject(e)// error catch
}
});
})
case “resolved”:
promise2=new myPromise(function(resolve,reject){
try{
let temple=onFullfilled(self.value);
//将上次一then里面的方法传递进下一个Promise的状态
resolve(temple);
}catch(e){
reject(e);//error catch
}
})
break;
case “rejected”:
promise2=new myPromise(function(resolve,reject){
try{
let temple=onRejected(self.reason);
//将then里面的方法传递到下一个Promise的状态里
resolve(temple);
}catch(e){
reject(e);
}
})
break;
default:
}
return promise2;
}
这样通过then方法返回一个promise就可以实现链式的调用:
p.then(function(x){console.log(x)}).then(function(){console.log(“链式调用1”)}).then(function(){console.log(“链式调用2”)}) //输出 1 链式调用1 链式调用2 这样我们虽然实现了then函数的链式调用,但是还有一个问题,就是在Promise/A+规范中then函数里面的onFullfilled方法和onRejected方法的返回值可以是对象,函数,甚至是另一个promise。
4.v4.0 then函数中的onFullfilled和onRejected方法的返回值问题 特别的为了解决onFullfilled和onRejected方法的返回值可能是一个promise的问题。
(1)首先来看promise中对于onFullfilled函数的返回值的要求
I)如果onFullfilled函数返回的是该promise本身,那么会抛出类型错误
II)如果onFullfilled函数返回的是一个不同的promise,那么执行该promise的then函数,在then函数里将这个promise的状态转移给新的promise III)如果返回的是一个嵌套类型的promsie,那么需要递归。
IV)如果返回的是非promsie的对象或者函数,那么会选择直接将该对象或者函数,给新的promise。
根据上述返回值的要求,我们要重新的定义resolve函数,这里Promise/A+规范里面称为:resolvePromise函数,该函数接受当前的promise、onFullfilled函数或者onRejected函数的返回值、resolve和reject作为参数。
下面我们来看resolvePromise函数的定义:
function resolvePromise(promise,x,resolve,reject){ if(promise===x){ throw new TypeError(“type error”) } let isUsed; if(x!==null&&(typeof x===”object”||typeof x===”function”)){ try{ let then=x.then; if(typeof then===”function”){ //是一个promise的情况 then.call(x,function(y){ if(isUsed)return; isUsed=true; resolvePromise(promise,y,resolve,reject); },function(e){ if(isUsed)return; isUsed=true; reject(e); }) }else{ //仅仅是一个函数或者是对象 resolve(x) } }catch(e){ if(isUsed)return; isUsed=true; reject(e); } }else{ //返回的基本类型,直接resolve resolve(x) } } 改变了resolvePromise函数之后,我们在then方法里面的调用也变成了resolvePromise而不是promise。
myPromise.prototype.then=function(onFullfilled,onRejected){
let self=this;
let promise2;
switch(self.status){
case “pending”:
promise2=new myPromise(function(resolve,reject){
self.onFullfilledArray.push(function(){
setTimeout(function(){
try{
let temple=onFullfilled(self.value);
resolvePromise(temple)
}catch(e){
reject(e) //error catch
}
})
});
self.onRejectedArray.push(function(){
setTimeout(function(){
try{
let temple=onRejected(self.reason);
resolvePromise(temple)
}catch(e){
reject(e)// error catch
}
})
});
})
case “resolved”:
promise2=new myPromise(function(resolve,reject){
setTimeout(function(){
try{
let temple=onFullfilled(self.value);
//将上次一then里面的方法传递进下一个Promise状态
resolvePromise(temple);
}catch(e){
reject(e);//error catch
}
})
})
break;
case “rejected”:
promise2=new myPromise(function(resolve,reject){
setTimeout(function(){
try{
let temple=onRejected(self.reason);
//将then里面的方法传递到下一个Promise的状态里
resolvePromise(temple);
}catch(e){
reject(e);
}
})
})
break;
default:
}
return promise2;
}
这样就能处理onFullfilled各种返回值的情况。
var p=new Promise(function(resolve,reject){resolve(“初始化promise”)}) p.then(function(){return new Promise(function(resolve,reject){resolve(“then里面的promise返回值”)})}).then(function(x){console.log(x)})
//输出 then里面promise的返回值 到这里可能有点乱,我们再理一理,首先返回值有两个:
then函数的返回值——>返回一个新promise,从而实现链式调用
then函数中的onFullfilled和onRejected方法——>返回基本值或者新的promise
这两者其实是有关联的,onFullfilled方法的返回值可以决定then函数的返回值。
五、最后补充Typescript实现的Promise/A+规范(可以忽略此节) interface IConstructor{ (resolve:IResolve,reject:IReject):void } interface IResolve { (x:any):void } interface IReject { (x:any):void } function myPromise(constructor:IConstructor):void{ let self:object=this; self.status=”pending”; self.value=undefined;//if pending->resolved self.reason=undefined;//if pending->rejected self.onFullfilledArray=[];//to deal with async(resolved) self.onRejectedArray=[];//to deal with async(rejeced) let resolve:IResolve; resolve=function(value:any):void{ //pending->resolved if(self.status===”pending”){ self.status=”resolved”; self.value=value; self.onFullfilledArray.forEach(function(f){ f(self.value); }) } } let reject:IReject; reject=function(reason:any):void{ if(self.status===”pending”){ self.status=”rejected”; self.reason=reason; self.onRejectedArray.forEach(function(f){ f(self.reason); }) } } //According to the definition that the function “constructor” accept two parameters //error catch try { constructor(resolve,reject); } catch (e) { reject(e); } }