共计 11248 个字符,预计需要花费 29 分钟才能阅读完成。
Promise 简介
是一个保留了异步事件将来执行后果的对象。它不是一个新技术,而是一种能够 优化异步编程编码格调的标准 。最早诞生于社区,用于解决 JavaScript 代码中 回调嵌套层级过多的问题和错误处理逻辑堆砌的问题。应用 Promise 对象封装异步操作,能够让代码变得直观、线性、优雅。
Promise 根本应用
用 Promise 封装 Ajax 申请
// 先写一个原始的 Ajax 申请
let xhr = new XMLHttpRequest()
function resolve(v){console.log(v);}
function reject(e){console.log(e);}
xhr.onerror = function(e){reject(e)
}
xhr.ontimeout = function(e){reject(e)
}
xhr.onreadystatechange = function(){if(xhr.readyState===4){if(xhr.status===200){resolve(xhr.response)
}
}
}
xhr.open('Get','https://wwww.google.com',true)
xhr.send()
// 第一版:利用 Promise 封装 ajax
let p = new Promise((resolve,reject)=>{let xhr = new XMLHttpRequest()
xhr.onerror = function(e){reject(e)
}
xhr.ontimeout = function(e){reject(e)
}
xhr.onreadystatechange = function(){if(xhr.readyState===4){if(xhr.status===200){resolve(xhr.response)
}
}
}
xhr.open('Get','https://wwww.google.com',true)
xhr.send()});
p.then(v=>{console.log("我是胜利时注册的回调");
},e=>{console.log("我是失败时注册的回调");
})
// 第二版 反对传参、封装了 Promise 创立的细节
function Xpromise(request){function executor(request,resolve,reject){let xhr = new XMLHttpRequest()
xhr.onerror = function(e){reject(e)
}
xhr.ontimeout = function(e){reject(e)
}
xhr.onreadystatechange = function(){if(xhr.readyState===4){if(xhr.status===200){resolve(xhr.response)
}
}
}
xhr.open('Get',request.url,true)
xhr.send()}
renturn new Promise(executor);
}
let x1 = Xpromise(makeRequest('https://wwww.google.com')).then(v=>{console.log("我是胜利时注册的回调");
},e=>{console.log("我是失败时注册的回调");
})
另外,Promise 还提供了一系列好用的 API, 如动态 resolve()、all()、race()办法等。
实现原理概述
Promise 用 回调函数提早绑定 、 回调函数 onResolve 返回值穿透 机制解决回调嵌套层级过多的问题;应用 谬误冒泡机制 简化了错误处理逻辑堆砌的问题。
手工实现一个 Promise
第一版
// 第一点:Promise 是一个类
class MyPromise {
// 第二点:Promised 构造函数的参数是一个函数;constructor(fn) {if (typeof fn !== "function") {throw new Error("promise 的结构函数参数应该为函数类型")
}
// 第三点:Promise 的外部状态有三个,Promise 对象具备值
this._status = PENDING;
this._value = undefined;
// 第五点:new Promise(fn)时,就须要执行 fn 做业务逻辑,故构造函数里就要调用 fn 函数。此处外部函数_resolve 和_reject 会被调用用于追踪 Promise 的外部状态
try {
// 留神用 try-catch 包住
fn(this._resolve.bind(this), this._reject.bind(this));
} catch (err) {this._reject(err)
}
}
// 第四点:定义 MyPromise 状态翻转时,要执行的外部函数
_resolve(val){if (this._status !== this.PENDING) return // 这段代码体现了 Promise 的状态翻转:只能是 P ->F 或者是 P ->R
this._status = FULLFILLED;
this._value = val;
};
_reject(err){if (this._status !== this.PENDING) return
this._status = REJECTED;
this._value = err;
};
}
第二版
class MyPromise {constructor(fn) {if (typeof fn !== "function") {throw new Error("myPromise 的结构函数参数应该为函数类型")
}
this._status = PENDING;
this._value = undefined;
// 个性 2 - 新增定义两个回调函数数组
this.fulfilledQueue = [];
this.rejectedQueue = [];
try {fn(this._resolve.bind(this), this._reject.bind(this));
} catch (err) {this._reject(err)
}
}
_resolve(val){if (this._status !== this.PENDING) return
// 个性 4:注册的回调函数在 Promise 状态翻转时会执行,执行的形式是循环从队列外面取出回调执行
// 定义 run 函数
run = (value)=>{
this._status = FULLFILLED;
this._value = val;
let ck;
while(ck = this.fulfilledQueue.shift()){ck(value);
}
}
// 个性 5:run()函数的执行,这里十分要害。要把 run 放进 setTimeOut。为什么?// 因为执行_resolve()函数时,then()可能还没执行,所以为了让 then()中的回调、包含链式调用的 then()的回调增加到 fulfilledQueue 中,// 须要提早执行 run()。实际上这里用 setTimeout 性能差,理论中采纳微工作的形式实现
setTimeout(run,0)
//run();};
_reject(err){if (this._status !== this.PENDING) return
run = (error)=>{
this._status = this.REJECTED;
this._value = err;
let ck;
while(ck = this.rejectedQueue.shift()){ck(error)
}
}
setTimeout(run,0);
};
// 最重要的 then 函数:// 个性 1 - 用于注册回调,then()函数有两个参数,都是可选的,如果参数不是函数将会被疏忽
// then()反对被同一个 Promise 屡次注册,个性 2 - 所以 Promise 外部要保护两个数组,别离存储 then 上注册的胜利回调和失败回调);// then()反对链式调用, 个性 3 - 之所以反对是因为其返回值是一个新的 Promise,此处要实现回调函数 onFulfilled 穿透机制的实现
// 个性 1 - 回调函数的注册: 故为它传递两个回调函数占位
then(onFulfilled,onRejected){const {_status, _value} = this;
// 个性 3 - 返回一个新的 promise
return new MyPromise((onFulfilledNext, onRejectedNext)=>{
let fulfilled = value => {
try{if(typeof onFulfilled != "function"){
// 如果 onFulfilled 或 onRejected 不是函数,onFulfilled 或 onRejected 被疏忽
onFulfilledNext(value)
}else{
// 如果 onFulfilled 或者 onRejected 返回一个值 res
let res = onFulfilled(value)
if(res instanceof MyPromise){// 若 res 为 Promise,这时后一个回调函数,就会期待该 Promise 对象 (即 res) 的状态发生变化,才会被调用,并且新的 Promise 状态和 res 的状态雷同
res.then(onFulfilledNext, onRejectedNext)
}else{
// 若 res 不为 Promise,则使 res 间接作为新返回的 Promise 对象的值
onFulfilledNext(res)
}
}
}catch(err){onRejectedNext(err)
}
}
let rejected = error => {
try{if(typeof onRejected != "function"){onRejectedNext(error)
}else{let res = onRejectedNext(error)
if(res instanceof MyPromise){res.then(onFulfilledNext, onRejectedNext)
}else{onFulfilledNext(res);
}
}
}catch(err){onRejectedNext(err)
}
}
switch(_status){
case PENDING:
// 在 promise 状态扭转前, onFulfilled 或者 onRejected 不可被调用
this._fulfilledQueue.push(onFulfilled)
this._rejectedQueue.push(onRejected)
break
case FULFILLED:
//onFulfilled 调用次数只有一次,fulfilled 对 onFulFilled 进行包装。why need 包装?因为 onFulFilled 可能是函数、可能不是,如果是函数,返回值可能是 Promise 可能不是
fulfilled(_value)
break
case REJECTED:
//onRejected 调用次数只有一次
rejected(_value)
break
}
})
}
}
第三版
class MyPromise {constructor(handle) {if (!isFunction(handle)) {throw new Error('MyPromise must accept a function as a parameter')
}
this._status = PENDING
this._value = undefined
this._fulfilledQueues = []
this._rejectedQueues = []
try {handle(this._resolve.bind(this), this._reject.bind(this))
} catch (err) {this._reject(err)
}
}
// 增加 resovle 时执行的函数
_resolve(val) {const run = () => {if (this._status !== PENDING) return
const runFulfilled = (value) => {
let cb;
while (cb = this._fulfilledQueues.shift()) {cb(value)
}
}
const runRejected = (error) => {
let cb;
while (cb = this._rejectedQueues.shift()) {cb(error)
}
}
/*
如果 resolve 的参数为 Promise 对象,则必须期待该 Promise 对象状态扭转后,
以后 Promsie 的状态才会扭转,且状态取决于参数 Promsie 对象的状态,因而这里进行逻辑辨别
*/
if (val instanceof MyPromise) {
val.then(value => {
this._value = value
this._status = FULFILLED
runFulfilled(value)
}, err => {
this._value = err
this._status = REJECTED
runRejected(err)
})
} else {
this._value = val
this._status = FULFILLED
runFulfilled(val)
}
}
setTimeout(run, 0)
}
_reject(err) {if (this._status !== PENDING) return
const run = () => {
this._status = REJECTED
this._value = err
let cb;
while (cb = this._rejectedQueues.shift()) {cb(err)
}
}
setTimeout(run, 0)
}
then(onFulfilled, onRejected) {const { _value, _status} = this
return new MyPromise((onFulfilledNext, onRejectedNext) => {
let fulfilled = value => {
try {if (!isFunction(onFulfilled)) {onFulfilledNext(value)
} else {let res = onFulfilled(value);
if (res instanceof MyPromise) {res.then(onFulfilledNext, onRejectedNext)
} else {onFulfilledNext(res)
}
}
} catch (err) {onRejectedNext(err)
}
}
let rejected = error => {
try {if (!isFunction(onRejected)) {onRejectedNext(error)
} else {let res = onRejected(error);
if (res instanceof MyPromise) {res.then(onFulfilledNext, onRejectedNext)
} else {onFulfilledNext(res)
}
}
} catch (err) {onRejectedNext(err)
}
}
switch (_status) {
case PENDING:
this._fulfilledQueues.push(fulfilled)
this._rejectedQueues.push(rejected)
break
case FULFILLED:
fulfilled(_value)
break
case REJECTED:
rejected(_value)
break
}
})
}
}
第四版 finally
class MyPromise {constructor(handle) {if (!isFunction(handle)) {throw new Error('MyPromise must accept a function as a parameter')
}
this._status = PENDING
this._value = undefined
this._fulfilledQueues = []
this._rejectedQueues = []
try {handle(this._resolve.bind(this), this._reject.bind(this))
} catch (err) {this._reject(err)
}
}
_resolve(val) {const run = () => {if (this._status !== PENDING) return
const runFulfilled = (value) => {
let cb;
while (cb = this._fulfilledQueues.shift()) {cb(value)
}
}
const runRejected = (error) => {
let cb;
while (cb = this._rejectedQueues.shift()) {cb(error)
}
}
if (val instanceof MyPromise) {
val.then(value => {
this._value = value
this._status = FULFILLED
runFulfilled(value)
}, err => {
this._value = err
this._status = REJECTED
runRejected(err)
})
} else {
this._value = val
this._status = FULFILLED
runFulfilled(val)
}
}
setTimeout(run, 0)
}
_reject(err) {if (this._status !== PENDING) return
const run = () => {
this._status = REJECTED
this._value = err
let cb;
while (cb = this._rejectedQueues.shift()) {cb(err)
}
}
setTimeout(run, 0)
}
then(onFulfilled, onRejected) {const { _value, _status} = this
return new MyPromise((onFulfilledNext, onRejectedNext) => {
let fulfilled = value => {
try {if (!isFunction(onFulfilled)) {onFulfilledNext(value)
} else {let res = onFulfilled(value);
if (res instanceof MyPromise) {res.then(onFulfilledNext, onRejectedNext)
} else {onFulfilledNext(res)
}
}
} catch (err) {onRejectedNext(err)
}
}
let rejected = error => {
try {if (!isFunction(onRejected)) {onRejectedNext(error)
} else {let res = onRejected(error);
if (res instanceof MyPromise) {res.then(onFulfilledNext, onRejectedNext)
} else {onFulfilledNext(res)
}
}
} catch (err) {onRejectedNext(err)
}
}
switch (_status) {
case PENDING:
this._fulfilledQueues.push(fulfilled)
this._rejectedQueues.push(rejected)
break
case FULFILLED:
fulfilled(_value)
break
case REJECTED:
rejected(_value)
break
}
})
}
// 增加 catch 办法
catch(onRejected) {return this.then(undefined, onRejected)
}
// 增加动态 resolve 办法
static resolve(value) {
// 如果参数是 MyPromise 实例,间接返回这个实例
if (value instanceof MyPromise) return value
return new MyPromise(resolve => resolve(value))
}
// 增加动态 reject 办法
static reject(value) {return new MyPromise((resolve, reject) => reject(value))
}
// 增加动态 all 办法
static all(list) {return new MyPromise((resolve, reject) => {
/**
* 返回值的汇合
*/
let values = []
let count = 0
for (let [i, p] of list.entries()) {
// 数组参数如果不是 MyPromise 实例,先调用 MyPromise.resolve
this.resolve(p).then(res => {values[i] = res
count++
// 所有状态都变成 fulfilled 时返回的 MyPromise 状态就变成 fulfilled
if (count === list.length) resolve(values)
}, err => {
// 有一个被 rejected 时返回的 MyPromise 状态就变成 rejected
reject(err)
})
}
})
}
// 增加动态 race 办法
static race(list) {return new MyPromise((resolve, reject) => {for (let p of list) {
// 只有有一个实例率先扭转状态,新的 MyPromise 的状态就跟着扭转
this.resolve(p).then(res => {resolve(res)
}, err => {reject(err)
})
}
})
}
finally(cb) {
return this.then(value => MyPromise.resolve(cb()).then(() => value),
reason => MyPromise.resolve(cb()).then(() => { throw reason})
);
}
}
其它问题
1. Promise 与宏观工作、async 函数等执行程序问题
Promise 是微工作的一种实现,给出如下的代码,剖析其输入程序
// 题目 1
async function a1 () {console.log('a1 start')
await a2()
console.log('a1 end')
}
async function a2 () {console.log('a2')
}
console.log('script start')
setTimeout(() => {console.log('setTimeout')
}, 0)
Promise.resolve().then(() => {console.log('promise1')
})
a1()
let promise2 = new Promise((resolve) => {resolve('promise2.then')
console.log('promise2')
})
promise2.then((res) => {console.log(res)
Promise.resolve().then(() => {console.log('promise3')
})
})
console.log('script end')
// 题目 2
async function async1() {console.log('async1 start');
await async2();
await async3()
console.log('async1 end');
}
async function async2() {console.log('async2');
}
async function async3() {console.log('async3');
}
console.log('script start');
setTimeout(function() {console.log('setTimeout');
}, 0)
async1();
new Promise(function(resolve) {console.log('promise1');
resolve();}).then(function() {console.log('promise2');
});
console.log('script end');
// 题目 3
async function async1(){console.log('async1 start')
await async2()
console.log('async1 end')
}
async function async2(){console.log('async2')
}
console.log('script start')
setTimeout(function(){console.log('setTimeout0')
},0)
setTimeout(function(){console.log('setTimeout3')
},3)
setImmediate(() => console.log('setImmediate'));
process.nextTick(() => console.log('nextTick'));
async1();
new Promise(function(resolve){console.log('promise1')
resolve();
console.log('promise2')
}).then(function(){console.log('promise3')
})
console.log('script end')
答案:
题目一:script start->a1 start->a2->promise2->script end->promise1->a1 end->promise2.then->promise3->setTimeout
题目二:script start->async1 start->async2->promise1->script end->async3->promise2->async1 end->setTimeout
在浏览器 console 可试验
题目三:script start->async1 start->async2->promise1->promise2
->script end->nextTick->async1 end->promise3->setTimeout->setImmediate->setTimeout3
在 node 环境中可试验
2. 如何实现 all、如何实现链式调用的
all()的实现:函数中保护了一个数组和计数器,数组的大小为初始时 all 函数中传递的 Promise 对象数量,数组存储各个 Promise 执行胜利后 resolve 失去的后果,每胜利一个计数器 +1,直到计数器累加到数组大小时即调用 resolve(value),只有有一个 Promise 执行到失败的回调,即全副失败。
链式调用的实现:then 函数返回的仍然是一个 Promise, 见第二版的 Promise 实现。
3. all 中任何一个 Promise 出错,导致其它正确的数据也无奈应用,如何解决呢?
办法 1:应用 allSettled 代替;办法 2:改写 Promise, 将 reject 操作换成是 resolve(new Error(“ 自定义的谬误 ”));办法 3:引入第三方库 promise-transaction
4.Promise 真的好用吗,它存在什么问题?
问题 1:没有提供中途勾销的机制;问题 2:必须要设置回调,否则外部谬误无奈在内部反映进去;问题 3:应用时仍然存在大量 Promise 的 api, 逻辑不清晰。