【进阶 第8期】Promise 原理
前言
提到Promise,我们脑海可能第一时间想到的关键词是javaScript异步编程,熟悉又很陌生,promise能做什么?解决了什么问题?实现原理是什么?
Promise 是异步编程的一种解决方案,比传统的解决方案——回调函数和事件——更合理和更强大。简单说就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果。从语法上说,Promise 是一个对象,从它可以获取异步操作的消息。Promise 提供统一的 API,各种异步操作都可以用同样的方法进行处理。
基本用法
1、实例化
语法:new Promise((resolove, reject) => {...})
new Promise(function(resolve, reject) {
// ... some code
if (/* 异步操作成功 */){
resolve(data);
} else {
reject(error);
}
});
2、Promise.prototype.then 方法
语法:promise.then(onFulfilled, onRejected)
promise.then(onFulfilled1, onRejected1).then(onFulfilled2, onRejected2)
3、Promise.prototype.catch 方法
语法:promise.catch(onFulfilled, onRejected)
new Promise((resolve,reject){
throw new Error('test')
}).catch(error=>{
console.log(error)
})
3、all、resolve、reject、race等静态方法
- Promise.all
- Promise.resolve
- Promise.reject
- Promise.race
// resolve、reject 相当于只是定义了一个有状态的Promise
Promise.resolve('成功结果').then(r=>{console.log(r)})
Promise.reject('失败结果').catch(err=>{console.log(err)})
// p1,p2,p3都成功的时候返回的是一个结果数组,而失败的时候则返回最先被reject失败状态的值
Promise.all([p1,p2,p3]).then([r1,r2,r2] => {},err=>{console.log(err)})
// p1,p2,p3哪个结果获得的快,就返回那个结果,不管结果本身是成功状态还是失败状态
Promise.race([p1,p2,p3]).then(res=>{console.log(res)},err=>{console.log(err)})
实现原理
回调地狱
在解释回调地狱之前,先来复习两个概念回调函数和: 同步vs异步
回调函数
当一个函数作为参数传入另一个参数中,并且它不会立即执行,只有当满足一定条件后该函数才可以执行,这种函数就称为回调函数。
异步任务
与之相对应的概念是“同步任务”。同步任务在主线程上排队执行,只有前一个任务执行完毕,才能执行下一个任务。异步任务不进入主线程,而是进入异步队列,前一个任务是否执行完毕不影响下一个任务的执行。
由于异步任务执行顺序不确定性,简单理解就是异步任务的回调函数执行不确定,因此为了保证回调函数执行顺序按照我们预期的结果来执行,平时coding工作中,我们经常大量使用的异步回调往往会发展成这个样子,举一个经典的ajax请求例子:
请求1(function(请求结果1){
请求2(function(请求结果2){
请求3(function(请求结果3){
请求4(function(请求结果4){
请求5(function(请求结果5){
...
})
})
})
})
})
发现这段代码回调函数写的太多了,回调函数层层嵌套,造成代码可读性非常差,后期也不好维护
寻求解决方案
为了解决可读性以及可维护性问题,我们可以做一些设想。
1、使用链式调用
new Promise(请求1)
.then(请求2(请求结果1))
.then(请求3(请求结果2))
.then(请求4(请求结果3))
.then(请求5(请求结果4))
.catch(处理异常(异常信息))
2、将异步转为接近同步代码的写法
let 请求结果1 = 请求1();
let 请求结果2 = 请求2(请求结果1);
let 请求结果3 = 请求3(请求结果2);
let 请求结果4 = 请求2(请求结果3);
let 请求结果5 = 请求3(请求结果4);
实现原理(核心逻辑实现)
1、三种状态
- Pending(进行中)
- Fulfilled(已成功)
- Rejected(已失败)
2、状态转换
- 只能有以下两种转换,Pending => Fulfilled 或 Pending => Rejected
- 对象的状态不受外界影响
- 状态改变之后不会再发生变化,会一直保持这个状态。
3、通过回调函数resolve 和 reject传递值
具体构造函数、实例方法、静态方法
4.1、构造函数
- 构造函数Promise必须接受一个函数handle作为参数
- 参数又包含两个函数 resolve 、reject
4.2、then 方法
- 接受两个参数,成功时回调
onFulfilled
, 失败时回调onRejected
, - 支持链式调用(then方法必返回一个promise对象)
4.3、all、resolve、reject、race
缺陷
- 无法取消Promise,一旦新建它就会立即执行,无法中途取消
- 内部错误无法反应到外部
手把手撸 —— 实现一个Promise 的 polyfill
第一步 定义三个状态
const PENDING = 'PENDING'
const FULFILLED = 'FULFILLED'
const REJECTED = 'REJECTED'
第二步 构造函数
- 传入fn,fn接收两个参数(resolve,reject)作为状态传递
class MPromise {
constructor(fn) {
// 初始化默认状态
this.status = PENDING
this.value = undefined
this.onFulfilledCallbacks = [];
this.onRejectedCallbacks = [];
// 执行fn 【resolve/reject 为伪代码】
// try catch 捕获执行中异常
try {
fn(resolve, reject);
} catch (e) {
reject(e);
}
}
}
第三步 resolve,reject回调函数
// 状态变更后调用resolve 或reject 分别处理成功、失败队列
class MPromise {
constructor(fn) {
const self = this;
this.value = null;
this.error = null;
this.status = PENDING;
this.onFulfilledCallbacks = [];
this.onRejectedCallbacks = [];
function resolve(value) {
if (value instanceof MPromise) {
return value.then(resolve, reject);
}
if (self.status === PENDING) {
setTimeout(() => {
self.status = FULFILLED;
self.value = value;
self.onFulfilledCallbacks.forEach((callback) => callback(self.value));
}, 0);
}
}
function reject(error) {
if (self.status === PENDING) {
setTimeout(function () {
self.status = REJECTED;
self.error = error;
self.onRejectedCallbacks.forEach((callback) => callback(self.error));
}, 0);
}
}
try {
fn(resolve, reject);
} catch (e) {
reject(e);
}
}
}
第四步 then 实现
1、如果当前状态已经转变为 FULFILLED 或 REJECTED,直接执行onFulfilled 或 onRejected的回调
2、将onFulfilled 和 onRejected,分别加入FULFILLED 和 REJECTED 回调队列管理。
3、由于then支持链式回调,返回值必须是MPromise实例
then(onFulfilled, onRejected) {
const self = this;
let bridgePromise;
onFulfilled =
typeof onFulfilled === "function" ? onFulfilled : (value) => value;
onRejected =
typeof onRejected === "function"
? onRejected
: (error) => {
throw error;
};
if (self.status === FULFILLED) {
return new MPromise((resolve, reject)=>{
try{
resove(onFulfilled(self.value));
}catch(e){
reject(e);
}
})
}
if (self.status === REJECTED) {
return new MPromise((resolve, reject)=>{
try{
reject(onRejected(self.error));
}catch(e){
reject(e);
}
})
}
if (self.status === PENDING) {
return new MPromise((resolve, reject) => {
self.onFulfilledCallbacks.push(onFulfilled);
self.onRejectedCallbacks.push(onRejected);
});
}
}
第五步 支持串行异步任务
以上代码中then目前支持同步任务。而用promise主要用来解决一组流程化的异步操作,因此需要把回调函数统一promiseify,以便于衔接各个回调函数(同步或异步)的处理结果
then(onFulfilled, onRejected) {
const self = this;
let bridgePromise;
onFulfilled =
typeof onFulfilled === "function" ? onFulfilled : (value) => value;
onRejected =
typeof onRejected === "function"
? onRejected
: (error) => {
throw error;
};
if (self.status === FULFILLED) {
return (bridgePromise = new MPromise((resolve, reject) => {
setTimeout(() => {
try {
let x = onFulfilled(self.value);
resolvePromise(bridgePromise, x, resolve, reject);
} catch (e) {
reject(e);
}
}, 0);
}));
}
if (self.status === REJECTED) {
return (bridgePromise = new MPromise((resolve, reject) => {
setTimeout(() => {
try {
let x = onRejected(self.error);
resolvePromise(bridgePromise, x, resolve, reject);
} catch (e) {
reject(e);
}
}, 0);
}));
}
if (self.status === PENDING) {
return (bridgePromise = new MPromise((resolve, reject) => {
self.onFulfilledCallbacks.push((value) => {
try {
let x = onFulfilled(value);
resolvePromise(bridgePromise, x, resolve, reject);
} catch (e) {
reject(e);
}
});
self.onRejectedCallbacks.push((error) => {
try {
let x = onRejected(error);
resolvePromise(bridgePromise, x, resolve, reject);
} catch (e) {
reject(e);
}
});
}));
}
}
function resolvePromise(bridgepromise, x, resolve, reject) {
//2.3.1规范,避免循环引用
if (bridgepromise === x) {
return reject(new TypeError("Circular reference"));
}
let called = false;
// 这个判断分支其实已经可以删除,用下面那个分支代替,因为promise也是一个thenable对象
if (x instanceof MPromise) {
if (x.status === PENDING) {
x.then(
(y) => {
resolvePromise(bridgepromise, y, resolve, reject);
},
(error) => {
reject(error);
}
);
} else {
x.then(resolve, reject);
}
// 2.3.3规范,如果 x 为对象或者函数
} else if (x != null && (typeof x === "object" || typeof x === "function")) {
try {
// 是否是thenable对象(具有then方法的对象/函数)
//2.3.3.1 将 then 赋为 x.then
let then = x.then;
if (typeof then === "function") {
// 2.3.3.3 如果 then 是一个函数,以x为this调用then函数,且第一个参数是resolvePromise,第二个参数是rejectPromise
then.call(
x,
(y) => {
if (called) return;
called = true;
resolvePromise(bridgepromise, y, resolve, reject);
},
(error) => {
if (called) return;
called = true;
reject(error);
}
);
} else {
//2.3.3.4 如果 then不是一个函数,则 以x为值fulfill promise。
resolve(x);
}
} catch (e) {
//2.3.3.2 如果在取x.then值时抛出了异常,则以这个异常做为原因将promise拒绝。
if (called) return;
called = true;
reject(e);
}
} else {
resolve(x);
}
}
其它API 实现
// catch方法其实是个语法糖,就是只传onRejected不传onFulfilled的then方法
catch(onRejected) {
return this.then(null, onRejected);
}
static race() {
return new MPromise(function (resolve, reject) {
for (let i = 0; i < promises.length; i++) {
promises[i].then(
function (data) {
resolve(data);
},
function (error) {
reject(error);
}
);
}
});
}
static all() {
return new MPromise(function (resolve, reject) {
let result = [];
let count = 0;
for (let i = 0; i < promises.length; i++) {
promises[i].then(
function (data) {
result[i] = data;
if (++count == promises.length) {
resolve(result);
}
},
function (error) {
reject(error);
}
);
}
});
}
static resolve(value) {
return new MPromise((resolve) => {
resolve(value);
});
}
static reject(error) {
return new MPromise((resolve, reject) => {
reject(error);
});
}
第七步 验证是否符合promise/A+标准
npm install -g promises-aplus-tests
promises-aplus-tests ./src/promise.js
Promise 的升级
ES6 出现了 generator 以及 async/await 语法,使异步处理更加接近同步代码写法,可读性更好,同时异常捕获和同步代码的书写趋于一致
(async ()=>{
let 下单 = await 点餐();
let 饭菜 = await 做饭(下单);
let 送饭结果 = await 送饭(饭菜);
let 通知结果 = await 通知我(送饭结果);
})();
拓展
- promise 对象
- promisesA+规范
- 从零开始写一个符合Promises/A+规范的promise