Promise是当前ES6语法中的异步解决方案,本文从基本概念开始,层层分析,一步一步手写实现Promise,但愿能和你们一块儿完全掌握Promise。 javascript
Promise是异步编程的一种解决方案,跟传统回调函数来实现异步相比,其设计更合理,代码更易懂。Promise是"美颜后"的异步解决方案.
在ES6中, 其写进了语言标准提供原生支持。
Promise有两大特色:html
pending
, fulfilled
, rejected
上图摘自MDNjava
Promise的具体用法能够参考阮一峰老师的 《ECMAScript 6 入门》或MDN。
这里有几个点须要注意:git
new Promise((resolve, reject) => {
resolve(1);//为了防止输出2,改为return resolve,平常编码中也要养成return resolve的习惯
console.log(2);// 能够输出2
}).then(r => {
console.log(r);
});
复制代码
reject=>{}
能够省略,建议使用 catch(error=>{})
,其不只可以获取reject的内容,还能够捕获到Promise函数和then函数中产生的异常。Promise内部的异常会被吃掉,不会被外部的异常捕获。若是熟悉了Promise的使用,其实咱们知道,Promise提供了异步编程的语法糖,使原来异步回调的操做能够用同步的方式来表达。es6
在传统AJAX异步解决方案中,咱们通常使用回调函数来解决数据的接收和处理,以下:github
$.get(url, (data) => {
console.log(data)
)
复制代码
在某些需求场景下,咱们须要发送多个异步请求,而且每一个请求之间结果之间须要相互依赖,随着回调函数相互嵌套的增长,函数之间的逻辑就会耦合在一块儿,难以维护,造成回调地狱。以下所示:面试
let country = 'china';
let city = 'shanghai';
let district = 'PD'
$.get(`xxxxx/countries`,countries=>{
/** **这里能够再第一个select控件中,渲染国家列表, **/
countries.forEach((item)=>{
if(item===country){
//查找并选中当前国家
$.get(`xxxxx/findCitiesByCountry/${country}`, cities => {
/** **这里能够再第二个select控件中,渲染城市列表, **/
cities.forEach((item)=>{
if(item === city){
//查找并选中当前城市
$.get(`xxxxx/findDistrictsByCity/${city}`, dists => {
/** **这里能够再第三个select控件中,渲染地区列表, **/
dists.forEach(item=>{
if(item==district){
//查找并选中地区
}
})
})
}
})
})
}
});
});
复制代码
上述是一个简单的三级联动功能,使用三个回调函数。它们相互嵌套逻辑复杂,耦合严重。
Promise解决了回调地狱问题,经过Promise将上述代码改写成编程
let country = 'china';
let city = 'shanghai';
let district = 'PD'
new Promise(() => {
$.get(`xxxxx/countries`, countries => {
return countries;
});
}).then((countries) => {
countries.forEach((item) => {
if (item === country) {
$.get(`xxxxx/findCitiesByCountry/${country}`, cities => {
return cities;
})
}
})
}).then((cities) => {
cities.forEach((item) => {
if (item === city) {
$.get(`xxxxx/findDistrictsByCity/${city}`, dists => {
return dists;
})
}
})
}).then((dists) => {
dists.forEach(item => {
if (item == district) {
//查找并选中地区
}
})
})
复制代码
此时,将异步执行由原来的回调,改为了 then...then....then...
链式调用的方式。
线性的链式执行(同步)更符合人类的思考习惯(更直白的说,按照顺序一步一步的闭环符合人类思考习惯。Promise就是将本来异步回调的语法形式,改写成同步。因此实现Promise,就是把异步回调这种丑陋的方式改为链式调用。经过手写Promise,咱们来理解和消化其设计思想。 数组
有了上述的铺垫,咱们了解Promise的概念和特征,也知道了Promise的优点,下面咱们一步步来实现Promise。promise
function Promise(executor){
try{
executor()
}catch(e){
console.log(e):
}
}
复制代码
function Promise(executor){
function resolve(value){
//能够将executor中的数据传入resolve中
}
function reject(value){
//能够将executor中的数据传入reject中
}
try{
executor(resolve,reject)
}catch(e){
console.log(e):
}
}
复制代码
PENDING=>FULFILLED
,在执行reject时,由 PENDING=>REJECTED
。const pending = 'PENDING';
const rejecting = 'REJECTED';
const fulfilled = 'FULFILLED';
function Promise(executor){
var that = this;
that.status = pending;
that.value = null;
that.error = null;
function resolve(val){
//当且仅当PENDING=》FULFILLED
if(that.status === pending){
that.status = fulfilled;
that.value = val;
}
}
function reject(val){
//当且仅当PENDING=》REJECTED
if(that.status === pending){
that.status = rejecting;
that.error = val;
}
}
try{
executor(resolve,reject);
}catch(e){
//在executor中产生的异常在reject中能够捕获。可是reject的异常,智能catch捕获
reject(e);
}
}
Promise.prototype.then = function(onFulfilled, onRejected){
var that = this;
if(that.status === fulfilled){
//当状态改变后,执行then中的回调
onFulfilled(that.value);
}
if(that.status === rejecting){
//同上
onRejected(that.error)
}
};
复制代码
执行以下代码
new Promise((resolve)=>{
resolve(1);
}).then((res)=>{
console.log(res);
});
复制代码
打印结果以下
若是executor函数存在异步,则须要等待resolve或者reject回调执行才会执行then中的函数体。
此处使用回调来解决异步:
第一步:定义两个Promise的成员:onResolvedCallBack和onRejectCallBack,存储then函数中的入参。
第二步:当执行then函数时,若是当前状态仍是PENDING(此时executor内部有异步操做)那么就将then中的参数传入onResolvedCallBack和onRejectCallBack中。若是此时状态是非PENDING,那么直接执行传入的函数便可。
第三步:Promise中的resolve函数执行时,触发onResolvedCallBack或者onRejectCallBack中的函数。
具体代码以下:
const pending = 'PENDING';
const rejecting = 'REJECTED';
const fulfilled = 'FULFILLED';
function Promise1(executor) {
var that = this;
that.status = pending;
that.value = null;
that.error = null;
that.resolvedCallbacks = [];
that.rejectedCallbacks = [];
function resolve(val) {
if (that.status === pending) {
that.status = fulfilled;
that.value = val;
that.resolvedCallbacks.map(cb => cb(that.value));
}
}
function reject(val) {
if (that.status === pending) {
that.status = rejecting;
that.error = val;
that.rejectedCallbacks.map(cb => cb(that.value));
}
}
try {
executor(resolve, reject);
} catch (e) {
reject(e);
}
}
Promise1.prototype.then = function (onFulfilled, onRejected) {
var that = this;
//为了保证兼容性,then的参数只能是函数,若是不是要防止then的穿透问题
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : v => v
onRejected =
typeof onRejected === 'function'
? onRejected
: r => {
throw r
}
if (that.status === pending) {
that.resolvedCallbacks.push(onFulfilled)
that.rejectedCallbacks.push(onRejected)
}
if (that.status === fulfilled) {
onFulfilled(that.value);
}
if (that.status === rejecting) {
onRejected(that.error)
}
};
复制代码
执行以下一段代码:
let p = new Promise((resolve) => {
setTimeout(() => {
resolve(1);
}, 3000)
});
p.then((res) => { console.log(res); });
console.log(2);
复制代码
打印结果以下:
let p = new Promise((resolve) => {
resolve(1);
});
p.then((res) => { console.log(res); });
console.log(2);
复制代码
此处咱们打印结果为
microtask和macrotask是Event Loop的知识点,关于Event Loop能够参考阮一峰老师的《JavaScript 运行机制详解:再谈Event Loop》
此处咱们使用setTimeout来模拟then的microtasks(注:)
function resolve(value) {
setTimeout(() => {
if (that.state === PENDING) {
that.state = RESOLVED
that.value = value
that.resolvedCallbacks.map(cb => cb(that.value))
}
}, 0)
}
function reject(value) {
setTimeout(() => {
if (that.state === PENDING) {
that.state = REJECTED
that.value = value
that.rejectedCallbacks.map(cb => cb(that.value))
}
}, 0)
}
复制代码
咱们执行以下代码:
let p = new Promise((resolve) => {
var a = new Promise((resolve) => { resolve(1) });
resolve(a);
});
p.then((res) => {
console.log(res);
});
复制代码
此处resolve传入的是Promise对象,打印结果为:
function resolve(value) {
if (val instanceof Promise) {
return val.then(resolve, reject);
}
setTimeout(() => {
if (that.state === PENDING) {
that.state = RESOLVED
that.value = value
that.resolvedCallbacks.map(cb => cb(that.value))
}
}, 0)
}
复制代码
在Promise中,在then中执行return语句,返回的必定是Promise对象,这也是then可以链式调用的缘由。
首先咱们将then中的以下片断
if (that.status === pending) {
that.resolvedCallbacks.push(onFulfilled)
that.rejectedCallbacks.push(onRejected)
}
复制代码
变形
if (that.status === pending) {
that.resolvedCallbacks.push(()=>{onFulfilled(that.value)});
that.rejectedCallbacks.push(()=>{onRejected(that.value)});
}
复制代码
它们之间只是写法的差别,效果相同。
由于咱们须要对then里传入的函数onFulfilled, onRejected返回的值进行判断,因此咱们须要对then继续改写
if (that.status === pending) {
that.resolvedCallbacks.push(()=>{const x = onFulfilled(that.value)});
that.rejectedCallbacks.push(()=>{const x = onRejected(that.value)});
}
复制代码
由于then返回的是Promise,因此继续完善
if (that.status === pending) {
return new Promise(resolve,reject){
that.resolvedCallbacks.push(()=>{const x = onFulfilled(that.value)});
that.rejectedCallbacks.push(()=>{const x = onRejected(that.error)});
}
}
复制代码
执行onFulfilled和onRejected时,使用try...catch...,因此继续完善
let promise2 = null;
if (that.status === pending) {
return promise2 = new Promise((resolve,reject)=>{
that.resolvedCallbacks.push(()=>{
try{
const x = onFulfilled(that.value);
}catch(e){
reject(e);
}
});
that.rejectedCallbacks.push(()=>{
try{
const x = onRejected(that.error);
}catch(e){
reject(e);
}
});
});
}
复制代码
上述x是onFulfilled(that.value)和onRejected(that.error)的返回值,为了保证then能够链式调用,也就是promise2的resolve可以resolve一个Promise对象,可是x返回的多是Promise对象,多是值,也多是函数,那么此处须要对x进行适配一下。此时引入resolvePromise函数,实现以下:
/** * 对resolve 进行改造加强 针对x不一样值状况 进行处理 * @param {promise} promise2 promise1.then方法返回的新的promise对象 * @param {[type]} x promise1中onFulfilled的返回值 * @param {[type]} resolve promise2的resolve方法 * @param {[type]} reject promise2的reject方法 */
function resolvePromise(promise2, x, resolve, reject) {
if (promise2 === x) {
// 若是从onFulfilled中返回的x 就是promise2 就会致使循环引用报错
return reject(new TypeError('循环引用'));
}
let called = false; // 避免屡次调用
// 若是x是一个promise对象 (该判断和下面 判断是否是thenable对象重复 因此无关紧要)
if (x instanceof Promise) { // 得到它的终值 继续resolve
if (x.status === PENDING) { // 若是为等待态需等待直至 x 被执行或拒绝 并解析y值
x.then(y => {
resolvePromise(promise2, y, resolve, reject);
}, reason => {
reject(reason);
});
} else {
// 若是 x 已经处于执行态/拒绝态(值已经被解析为普通值),用相同的值执行传递下去
x.then(resolve, reject);
}
// 若是 x 为对象或者函数
} else if (x != null && ((typeof x === 'object') || (typeof x === 'function'))) {
try { // 是不是thenable对象(具备then方法的对象/函数)
let then = x.then;
if (typeof then === 'function') {
then.call(x, y => {
if(called) return;
called = true;
resolvePromise(promise2, y, resolve, reject);
}, reason => {
if(called) return;
called = true;
reject(reason);
})
} else { // 说明是一个普通对象/函数
resolve(x);
}
} catch(e) {
if(called) return;
called = true;
reject(e);
}
} else {
resolve(x);
}
}
复制代码
此时that.status === pending的代码块也要继续修改
if (that.status === pending) {
return promise = new Promise1((resolve, reject) => {
that.resolvedCallbacks.push(() => {
try {
const x = onFulfilled(that.value);
resolvePromise(promise,x,resolve,reject);
} catch (e) {
reject(e);
}
});
that.rejectedCallbacks.push(() => {
try {
const x = onRejected(that.error);
} catch (e) {
reject(e);
}
});
})
}
复制代码
同理that.status===fulfilled和that.status===rejecting的时候代码以下:
if (that.status === FULFILLED) { // 成功态
return promise2 = new Promise((resolve, reject) => {
setTimeout(() => {
try{
let x = onFulfilled(that.value);
resolvePromise(promise2, x, resolve, reject);
} catch(e) {
reject(e);
}
});
})
}
if (that.status === REJECTED) {
return promise2 = new Promise((resolve, reject) => {
setTimeout(() => {
try {
let x = onRejected(that.reason);
resolvePromise(promise2, x, resolve, reject);
} catch(e) {
reject(e);
}
});
});
}
复制代码
Promise.all的用法以下
const p = Promise.all([p1, p2, p3]).then((resolve)=>{},(reject)=>{});
复制代码
Promise.all方法接受一个数组做为参数,只有当数组的全部Promise对象的状态所有fulfilled,才会执行后续的then方法。
根据all的用法和特色,咱们推测Promise.all返回一个Promise对象,在Promise对象中去等待Promise数组对象的函数执行完毕,数组中的每一个对象执行完毕都+1,当等于数组的长度时,resolve数组对象中全部resolve出来的值。
Promise.all = function(promises) {
return new Promise((resolve, reject) => {
let done = gen(promises.length, resolve);
promises.forEach((promise, index) => {
promise.then((value) => {
done(index, value)
//每执行一次都会往values数组中放入promise对象数组成员resolve的值
//当values的长度等于promise对象数组的长度时,resolve这个数组values
}, reject)
})
})
}
//使用闭包,count和values在函数的声明周期中都存在
function gen(length, resolve) {
let count = 0;
let values = [];
return function(i, value) {
values[i] = value;
if (++count === length) {
console.log(values);
resolve(values);
}
}
}
复制代码
Promise.race的用法和all相似,区别就是promise对象数组其中有一个fulfilled,就执行then方法,实现以下
Promise.race = function(promises) {
return new Promise((resolve, reject) => {
promises.forEach((promise, index) => {
promise.then(resolve, reject);
});
});
}
复制代码
须要注意的是all和race函数中的数组成员不必定是Promise对象,若是不是Promise提供了resolve方法将其转化成Promise对象。resolve的实现很简单以下:
Promise.resolve = function (value) {
return new Promise(resolve => {
resolve(value);
});
}
复制代码
《ES6入门-阮一峰》
《ES6 系列之咱们来聊聊 Promise》
《异步解决方案----Promise与Await》
《Promise原理讲解 && 实现一个Promise对象》
《面试精选之Promise》
《Promise/A+》
《Promise之你看得懂的Promise》
《八段代码完全掌握 Promise》
《Promise不会??看这里!!!史上最通俗易懂的Promise!!!》
《Promise 必知必会(十道题)》