前言
Promise 是 ES6 新增的一个内置对象, 它是用来避免回调地狱的一种解决方案。
从以前一直嵌套传回调函数,到使用 Promise 来链式异步回调。Promise 究竟是怎么实现,从而达到回调函数“扁平化”?
接下来就来一步步实现一个简单的 Promise。开始发车了...
执行步骤
先来看一个使用 Promise 的简单例子:
var p = new Promise(function a (resolve) {
console.log(1);
setTimeout(function () {
resolve(2);
}, 1000);
})
p.then(function b (val) {
console.log(val);
});
复制代码
代码执行,它会先执行函数 a,打印出 1。定时器 1 秒后执行 resolve,紧接着执行了函数 b。
更详细的步骤是这样子的:
- new Promise,执行 Promise 构造函数;
- 构造函数里,执行 a 函数;
- 执行 then 函数;
- 1 秒后,执行 resolve 函数;
- 执行 b 函数。
这里的一个思路就是,在 then 函数执行时用一个属性保存函数 b,然后在 resolve 执行时再将其执行。
开始封装
这里定义一个 MyPromise,它有 then 函数,还有一个 callback
属性用来保存上面的“b 函数”。
function MyPromise (fn) {
var _this = this;
// 用来保存 then 传入的回调函数
this.callback = undefined;
function resolve (val) {
_this.callback && _this.callback(val);
}
fn(resolve);
}
MyPromise.prototype.then = function (cb) {
this.callback = cb;
};
复制代码
测试使用:
var p = new MyPromise(function (resolve) {
console.log(1);
setTimeout(function () {
resolve(2);
}, 1000);
});
p.then(function (val) {
console.log(val);
});
复制代码
代码执行时会马上打印出 1,1秒后打印出 2。毛问题。
多个 resolve 调用处理
上面已经实现了一个简单的 Promise,当然还有很多种情况需要考虑。
比如会有这么一种情况,调用了多个 resolve 函数:
var p = new MyPromise(function (resolve) {
console.log(1);
setTimeout(function () {
resolve(2);
resolve(3);
resolve(4);
}, 1000);
});
复制代码
原生的 Promise 在调用了第一个 resolve 之后,后面的 resolve 都无效化,即后面的 resolve 都是没用的代码。
这里的处理方式是,给 MyPromise 再添加一个属性 isResolved
,用来记录是否调用过 resolve 函数。如果调用过,用它标识一下。再有 resolve 的调用,则用它判断返回。
function MyPromise (fn) {
var _this = this;
this.callback = undefined;
this.isResolved = false;
function resolve (val) {
if (_this.isResolved) return;
_this.isResolved = true;
_this.callback && _this.callback(val);
}
fn(resolve);
}
复制代码
多个 then 处理
继续走,除了可以调用多个 resolve 函数,同样我们可以调用多个 then 函数。
var p = new MyPromise(function (resolve) {
console.log(1);
setTimeout(function () {
resolve(2);
}, 1000);
});
p.then(function (val) {
console.log(val);
});
p.then(function (val) {
console.log(val);
});
复制代码
与 resolve 不同,这里的每一个传给 then 的回调函数都会在 1 秒后执行,即 then 函数都有效。代码执行,先打印出 1。1 秒后,打印两个 2。
所以 MyPromise 的 callback 属性需改成数组
的格式,保存着每一个 then 的回调函数。
function MyPromise (fn) {
var _this = this;
this.callback = [];
this.isResolved = false;
function resolve (val) {
if (_this.isResolved) return;
_this.isResolved = true;
if (_this.callback.length > 0) {
_this.callback.forEach(function (func) {
func && func(val);
});
}
}
fn(resolve);
}
MyPromise.prototype.then = function (cb) {
this.callback.push(cb);
};
复制代码
在多次调用 then 时,MyPromise 通过属性 callback 来保存多个回调函数。在 resolve 执行后,再去遍历 callback,将它保存的回调函数逐个执行。
支持 then 链式调用
Promise 相对于回调地狱而言,它的优势在于可以进行 then 的链式调用,从而将回调函数“扁平化”。
比如我有一个异步操作需要在另一个异步操作后才能执行,只需要继续调用 then 就能够下一步回调。
var p = new MyPromise(function (resolve) {
console.log(1);
setTimeout(function () {
resolve(2);
}, 1000);
});
p.then(function (val) {
console.log(val);
}).then(function (val) {
console.log(val);
});
复制代码
既然要能够 then 链式调用,那我在执行完 then 函数后返回 this 不就可以啦。但这不就跟刚刚的代码一样了吗?
p.then(function (val) {
console.log(val);
});
p.then(function (val) {
console.log(val);
});
复制代码
这样就会在调用 resolve 函数之后同时执行,而不是执行完第一个 then 后,再执行第二个。所以直接返回 this 的方案是不行的。
我们再想,还有什么可以返回的,并且带有 then 函数的。答案就是 new 一个新的 MyPromise 并返回。我们需重写一个 then 函数的实现。
MyPromise.prototype.then = function (cb) {
var _this = this;
return new MyPromise(function (resolve) {
_this.callback.push({
cb: cb,
resolve: resolve
});
});
};
复制代码
这里 callback 重新改写了一下,保存的是 then 的回调函数,和新 new 的 MyPromise 的 resolve 函数。保存的 resolve 函数先不执行,因为我们知道,它一旦执行了,就会触发传入 then 的回调函数的执行。
同时,MyPromise 构造函数里的 resolve 也需要调整一下:
function MyPromise (fn) {
var _this = this;
this.callback = [];
this.isResolved = false;
function resolve (val) {
if (_this.isResolved) return;
_this.isResolved = true;
if (_this.callback.length > 0) {
_this.callback.forEach(function (item) {
var res;
var cb = item.cb;
var resolve = item.resolve;
cb && (res = cb(val));
resolve && resolve(res);
});
}
}
fn(resolve);
}
复制代码
在执行第一个 then 的回调函数时,将其执行完返回的值,作为保存的 resolve 的参数传入。
p.then(function (val) {
console.log(val);
return val + 1;
}).then(function(val) {
console.log(val);
});
复制代码
这样子,就能够链式的 then 调用。先别急,我们实现的只是 then 的同步
链式调用,而我们最终要的是异步
的链式调用。
我们需要这样子的:
p.then(function (val) {
console.log(val);
return new MyPromise(function (resolve) {
setTimeout(function () {
resolve(val + 1);
}, 1000);
});
}).then(function(val) {
console.log(val);
});
复制代码
先打印出 1,1秒后打印出第一个 then 里的 2,再过多一秒,打印出第二个 then 的 3。
所以,我们需要在取出原来保存的 cb 返回的值进行判断。如果该值是一个 MyPromise 对象,则调用它的 then,否则跟原来一样调用。
function MyPromise (fn) {
var _this = this;
this.callback = [];
this.isResolved = false;
function resolve (val) {
if (_this.isResolved) return;
_this.isResolved = true;
if (_this.callback.length > 0) {
_this.callback.forEach(function (item) {
var res;
var cb = item.cb;
var resolve = item.resolve;
cb && (res = cb(val));
if (typeof res === 'object' && res.then) {
res.then(resolve);
} else {
resolve && resolve(res);
}
});
}
}
fn(resolve);
}
复制代码
最后
在我们实现的 MyPromise 里,有两个属性,分别是 isResolved
和 callback
。isResolved 是一个标识,用来防止多次调用 resolve。callback 是一个数组,用来保存回调函数。
MyPromise 还有一个 then 函数,用来处理异步后的回调,能够链式异步调用。
这样子就实现了一个简单的 Promise,完整的代码戳 这里。