Promise简介
Promise 是异步编程的一种解决方案,比传统的异步解决方案【回调函数】和【事件】更合理、更强大。现已被 ES6 纳入进规范中。
使用方式
new Promise((resolve, reject) => {
ajax({
success(res) {
resolve(res);
},
error(err) {
reject(err);
}
})
}).then((res) => {
// 请求正常处理
})
.catch((err) => {
// 错误处理
})
基本源码分析
成功回调处理
首先我们需要一个promise的使用实例代码,如下:
示例代码1
new Promise((resolve) => {
setTimeout(() => {
resolve(123);
}, 0);
}).then((res) => {
console.log(res);
})
事例代码中,首先new了一个Promise对象,源码中Promise对象的构造函数长这样:
Promise构造函数
function Promise(fn) {
if (typeof this !== 'object') {
throw new TypeError('Promises must be constructed via new');
}
if (typeof fn !== 'function') {
throw new TypeError('Promise constructor’s argument is not a function');
}
this._deferredState = 0; // then的状态
this._state = 0; // 状态,初始状态是0表示pending,1表示fulfilled,2表示rejected
this._value = null; // 获取到的值
this._deferreds = null; // 存储的成功回调和失败回调,以及返回的新Promis的handler
if (fn === noop) return;
doResolve(fn, this);
}
先检测是否用new的方式实例化promise对象,再检查传入的方法是否为方法,若检查都通过,就初始化一些参数,之后将传入promise的方法fn和promise实例作为参数传给doResolve方法。doResolve方法如下:
doResolve方法
function doResolve(fn, promise) {
var done = false;
var res = tryCallTwo(fn, function (value) {
if (done) return;
done = true;
resolve(promise, value);
}, function (reason) {
if (done) return;
done = true;
reject(promise, reason);
});
if (!done && res === IS_ERROR) {
done = true;
reject(promise, LAST_ERROR);
}
}
tryCallTwo方法接受3个参数,第一个是传给Promise的异步方法fn,真实调用的resolve方法,以及真实调用的reject方法。tryCallTwo方法如下:
tryCallTwo
function tryCallTwo(fn, a, b) {
try {
fn(a, b); // 执行传入promise的方法,并且将resolve和reject方法作为参数传入
} catch (ex) {
LAST_ERROR = ex;
return IS_ERROR;
}
}
异步操作还未返回
因为我们的示例只是一个setTimeout的一个回调,所以不会走到报错的环节,setTimeout会放入任务队列中,延迟执行resolve。接下来会先执行then方法,Promise中的then方法挂在prototype上面:
then方法
Promise.prototype.then = function(onFulfilled, onRejected) {
if (this.constructor !== Promise) {
return safeThen(this, onFulfilled, onRejected);
}
var res = new Promise(noop);
handle(this, new Handler(onFulfilled, onRejected, res));
return res;
};
- 如果不是在Promise实例上调用then会调用safeThen(这一段我也不是很理解,会有不在Promise实例调用then的情况嘛?希望有大神能解释)。
- 若是正常Promise实例调用then,则新建一个Promise实例(then中返回的实例,用于链式调用)
- 把正确的回调和报错的回调以及返回的Promise实例都作为参数,传入Handler的构造方法中。Handler构造方法如下:
Handler构造函数
function Handler(onFulfilled, onRejected, promise){
this.onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : null;
this.onRejected = typeof onRejected === 'function' ? onRejected : null;
this.promise = promise;
}
Handler的构造方法仅仅只是将成功回调和失败回调,以及返回的下一个Promise实例作为私有变量保存下来。接着将Handler实例作为参数传入handle方法中:
handle(this, new Handler(onFulfilled, onRejected, res));
handle方法代码如下:
handle方法
function handle(self, deferred) {
while (self._state === 3) { // 处理resolve的值是个promise实例的情况
self = self._value;
}
if (Promise._onHandle) { // 若定义了_onHandle方法,则调用
Promise._onHandle(self);
}
if (self._state === 0) { // 异步请求还没返回
if (self._deferredState === 0) { // then方法之前还未调用
self._deferredState = 1; // 进入改逻辑说明then已经调用,改为状态为2
self._deferreds = deferred; // handler
return;
}
if (self._deferredState === 1) { // Promise实例又执行了一遍then
self._deferredState = 2; // 在请求未返回情况下,第二次调用then,状态为2
self._deferreds = [self._deferreds, deferred]; // 在请求未返回情况下,第二次调用then,deferreds改成数组,存储多个handler
return;
}
self._deferreds.push(deferred); // 在请求未返回情况下,多次调用then,将handler依次放入队列
return;
}
handleResolved(self, deferred);
}
此时我们的逻辑走到这里,把then状态修改为1,存储了handler后返回:
if (self._deferredState === 0) {
self._deferredState = 1;
self._deferreds = deferred;
return;
}
异步请求返回啦
回顾doResolve方法:
function doResolve(fn, promise) {
var done = false;
var res = tryCallTwo(fn, function (value) {
if (done) return;
done = true;
resolve(promise, value); // 实例代码中resolve(123),所以逻辑走到这里
}, function (reason) {
if (done) return;
done = true;
reject(promise, reason);
});
if (!done && res === IS_ERROR) {
done = true;
reject(promise, LAST_ERROR);
}
}
resolve方法如下:
resolve方法
function resolve(self, newValue) {
// Promise Resolution Procedure: https://github.com/promises-aplus/promises-spec#the-promise-resolution-procedure
if (newValue === self) {
return reject(
self,
new TypeError('A promise cannot be resolved with itself.')
);
}
if (
newValue &&
(typeof newValue === 'object' || typeof newValue === 'function')
) {
var then = getThen(newValue);
if (then === IS_ERROR) {
return reject(self, LAST_ERROR);
}
if (
then === self.then &&
newValue instanceof Promise
) {
self._state = 3;
self._value = newValue;
finale(self);
return;
} else if (typeof then === 'function') {
doResolve(then.bind(newValue), self);
return;
}
}
self._state = 1;
self._value = newValue;
finale(self);
}
此时我们只是resolve了一个123,既不是对象也不是Promise,于是只走这段逻辑:
self._state = 1; // state为1表示fulfilled
self._value = newValue; // 123
finale(self);
执行finale方法:
finale
function finale(self) {
if (self._deferredState === 1) { // then已经执行
handle(self, self._deferreds); // 走到这里
self._deferreds = null;
}
if (self._deferredState === 2) { // 多从调用then,则通知所有then注册的回调全部执行
for (var i = 0; i < self._deferreds.length; i++) {
handle(self, self._deferreds[i]);
}
self._deferreds = null;
}
}
再次执行handle方法,此时state和defferedSate值都为1,则执行handleResolved方法
handleResolved(self, deferred);
handleResolved
function handleResolved(self, deferred) {
asap(function() {
var cb = self._state === 1 ? deferred.onFulfilled : deferred.onRejected;
if (cb === null) {
if (self._state === 1) {
resolve(deferred.promise, self._value);
} else {
reject(deferred.promise, self._value);
}
return;
}
var ret = tryCallOne(cb, self._value);
if (ret === IS_ERROR) {
reject(deferred.promise, LAST_ERROR);
} else {
resolve(deferred.promise, ret);
}
});
}
- asap是一个第三方库,功能类似与setImmediate
- 判断当前state的状态,决定要执行的回调是成功回调还是失败回调
- 若没设置过回调,则把value传给下一个Promise
- 执行tryCallOne
tryCallOne
function tryCallOne(fn, a) {
try {
return fn(a);
} catch (ex) {
LAST_ERROR = ex;
return IS_ERROR;
}
}
在tryCallOne中执行对应回调,若没有报错的话,将返回值作为value传递给下一个Promise。我们的示例代码中的console.log(res)就会执行,打印出123。
一次不报错的Promise调用流程
我们整理一下,如果调用一次Promise,且没发生报错的情况下,发生了什么事情
错误回调处理
若Promise中传入的方法,发生错误,这时候会发生什么?
示例代码2
new Promise((resolve) => {
resolve(a); // a并没有定义,发生报错
}).then((res) => {
console.log(res);
})
回到源码中到doResolve方法以及内部的tryCallTwo方法
// doResolve
function doResolve(fn, promise) {
var done = false;
var res = tryCallTwo(fn, function (value) {
if (done) return;
done = true;
resolve(promise, value);
}, function (reason) {
if (done) return;
done = true;
reject(promise, reason);
});
if (!done && res === IS_ERROR) {
done = true;
reject(promise, LAST_ERROR);
}
}
// tryCallTwo
function tryCallTwo(fn, a, b) {
try {
fn(a, b);
} catch (ex) {
LAST_ERROR = ex;
return IS_ERROR;
}
}
tryCallTwo方法里使用了try-catch,若发生报错LAST_ERROR会保存这次报错的信息,并返回一个错误。之后promise会自动执行reject。
reject
function reject(self, newValue) {
self._state = 2;
self._value = newValue;
if (Promise._onReject) {
Promise._onReject(self, newValue);
}
finale(self);
}
reject跟resolve方法类似,只是把state状态变成了2而已,value保存为这次报错的信息。然后then方法中调用handleResolved时候,选择执行第二个传入的方法。
var cb = self._state === 1 ? deferred.onFulfilled : deferred.onRejected;
但我们大多数开发的时候,then都没有传入第二个方法,而是使用catch来捕获错误。
new Promise((resolve) => {
resolve(a); // a并没有定义,发生报错
}).then((res) => {
console.log(res);
})
.catch((err) => {
// ...
})
catch的实现非常简单,只是调用了一下then方法,但是不传入正确回调的方法。
catch
Promise.prototype['catch'] = function (onRejected) {
return this.then(null, onRejected);
};
不过有一种情况,在异步操作返回之后的报错,try-catch无法捕获到,这时候需要在返回的方法体中手动reject报错信息。
new Promise((resolve, reject) => {
setTimeout(() => {
try {
resolve(a); // a并没有定义,发生报错
} catch(err) {
reject(err); // 因为异步的报错,需要手动reject
}
}, 0);
}).then((res) => {
console.log(res);
})
.catch((err) => {
// ...
})
Promise正常使用的流程图
resolve一个Promise实例
实例代码3
new Promise((resolve) => {
resolve(new Promise((resolve) => {
resolve(111);
}));
}).then((res) => {
console.log(111);
});
resolve方法里面,如果resolve的是一个Promise会走入以下逻辑:
if (
then === self.then &&
newValue instanceof Promise
) {
self._state = 3;
self._value = newValue;
finale(self);
return;
}
状态值设置为3,并且把resolve内部的Promise实例赋值给value。 handle方法中的处理
while (self._state === 3) { // state状态为3的时候,把resolve内的promise设置为当前promise
self = self._value;
}
}
于是Promise内resolve一个Promise的时候,接下来调用的then方法中回调中的参数是resolve内部的Promise的value。代码3中打印出来的值应该是111。
then中return一个Promise
链式调用then的时候,then中的回调执行的值,将作为返回的Promise的value,也就是下一次then时候传给回调的值。
new Promise((resolve) => {
resolve(1);
}).then((resolve) => {
resolve(2);
}).then((res) => {
console.log(res); // 2
})
在Promise代码执行完毕后,handleResolved方法后面还有一个这样的逻辑
var ret = tryCallOne(cb, self._value);
if (ret === IS_ERROR) {
reject(deferred.promise, LAST_ERROR);
} else {
resolve(deferred.promise, ret);
}
如果回调方法没有报错,回调函数return的值将用resolve的方式作为value传给then中返回的Promise。那如果return一个Promise将会和resolve(new Promise)一样,return的Promise将取代原先then中返回的Promise。下一个then中传递的参数也将是手动return的Promise的value。如:
new Promise((resolve) => {
resolve(1);
}).then((res) => {
return new Promise((resolve) => {
resolve(2);
});
}).then((res) => {
console.log(res) // 2
})
Promise.all
有时候需要多个请求并行发送,就要使用到Promise.all
Promise.all源码
Promise.all = function (arr) {
var args = Array.prototype.slice.call(arr);
return new Promise(function (resolve, reject) {
if (args.length === 0) return resolve([]);
var remaining = args.length;
function res(i, val) {
if (val && (typeof val === 'object' || typeof val === 'function')) {
if (val instanceof Promise && val.then === Promise.prototype.then) {
while (val._state === 3) {
val = val._value;
}
if (val._state === 1) return res(i, val._value);
if (val._state === 2) reject(val._value);
val.then(function (val) {
res(i, val);
}, reject);
return;
} else {
var then = val.then;
if (typeof then === 'function') {
var p = new Promise(then.bind(val));
p.then(function (val) {
res(i, val);
}, reject);
return;
}
}
}
args[i] = val;
if (--remaining === 0) {
resolve(args);
}
}
for (var i = 0; i < args.length; i++) {
res(i, args[i]);
}
});
};
可以看出Promise.all也是返回了一个Promise,这个Promise内部resolve了一个返回所有结果的数组。Promise.all会接受一个数组,之后遍历这个数组,数组的每个成员都会执行一遍res这个方法
res
function res(i, val) {
if (val && (typeof val === 'object' || typeof val === 'function')) {
if (val instanceof Promise && val.then === Promise.prototype.then) {
while (val._state === 3) {
val = val._value;
}
if (val._state === 1) return res(i, val._value);
if (val._state === 2) reject(val._value);
val.then(function (val) {
res(i, val);
}, reject);
return;
} else {
var then = val.then;
if (typeof then === 'function') {
var p = new Promise(then.bind(val));
p.then(function (val) {
res(i, val);
}, reject);
return;
}
}
}
args[i] = val;
if (--remaining === 0) {
resolve(args);
}
}
判断传入Promise实例的状态:
如果state为3(里面resolve了Promise)当前的Promise更换为value。
state为2说明,队列中的该Promise内部状态为reject,则直接在Promise.all执行reject。所以为什么Promise.all中,队列中有一个Promise实行失败,整个Promise.all都会进入失败回调。
state为1,则直接调用res(i, value)。
state状态为0,改promise成员直接调用then方法,获取其中value,再调用res(i, value)。
当队员成员Promise的value已经返回后,再次调用res(i, value),此时的value已经不是个Promise,将值赋到数组对应位子。
args[i] = val;
全部Promise成员执行完毕后,resolve数组
if (--remaining === 0) {
resolve(args);
}
Promise.resolve
Promise.resolve源码
var TRUE = valuePromise(true);
var FALSE = valuePromise(false);
var NULL = valuePromise(null);
var UNDEFINED = valuePromise(undefined);
var ZERO = valuePromise(0);
var EMPTYSTRING = valuePromise('');
function valuePromise(value) {
var p = new Promise(Promise._noop);
p._state = 1;
p._value = value;
return p;
}
Promise.resolve = function (value) {
if (value instanceof Promise) return value;
if (value === null) return NULL;
if (value === undefined) return UNDEFINED;
if (value === true) return TRUE;
if (value === false) return FALSE;
if (value === 0) return ZERO;
if (value === '') return EMPTYSTRING;
if (typeof value === 'object' || typeof value === 'function') {
try {
var then = value.then;
if (typeof then === 'function') {
return new Promise(then.bind(value));
}
} catch (ex) {
return new Promise(function (resolve, reject) {
reject(ex);
});
}
}
return valuePromise(value);
};
这里用到了一个valuePromise的方法,这个方法接受一个值,并且设置一个新的Promise,将这个Promise的状态直接设置为1,value设置为传入值,直接返回这个Promise。
function valuePromise(value) {
var p = new Promise(Promise._noop);
p._state = 1;
p._value = value;
return p;
}
Promise.all要分为3中情况分析:
1.传入一个值(若是对象或方法,则其中没有then的私有方法),那么直接将这个值传给valuePromise,并将其返回
if (value === null) return NULL;
if (value === undefined) return UNDEFINED;
if (value === true) return TRUE;
if (value === false) return FALSE;
if (value === 0) return ZERO;
if (value === '') return EMPTYSTRING;
return valuePromise(value);
用法上:
Promise.resolve(1).then((res) => {
console.log(res) // 打印出1,Promise.resolve直接返回了一个新的Promise,它的值是1
})
2.传入一个Promise,则直接将这个Promise返回。
f (value instanceof Promise) return value;
3.传入一个含有then方法的对象或方法,会调用它的then方法。
try {
var then = value.then;
if (typeof then === 'function') {
return new Promise(then.bind(value));
}
} catch (ex) {
return new Promise(function (resolve, reject) {
reject(ex);
});
}
Promise.finally
then方法只在promise内方法执行成功时执行;catch方法只在Promise内方法执行失败了执行;有些时候不管方法体执行成功或失败,都要执行某些特定方法,这个时候就要使用finally。
示例代码4
new Promise((resolve) => {
resolve(1);
}).then((res) => {
...
})
catch((err) => {
...
})
.finally(() => {
...
})
源码中finally的代码如下:
Promise.prototype.finally = function (f) {
return this.then(function (value) {
return Promise.resolve(f()).then(function () {
return value;
});
}, function (err) {
return Promise.resolve(f()).then(function () {
throw err;
});
});
};
finally中只是返回了this.then,然后在then中的成功失败回调中都执行了传入的f方法。不过既然返回了this.then,也就意味着,finally后面可以继续链式调用then。
总结
Promise是一个构造函数,里面有2个核心的控制状态的变量:
- state主体方法执行的状态值,0代表初始值,1代表请求成功,2代表失败,3代表resolve了一个Promise实例
- defferedState是then执行的状态值,0代表then方法未执行,1代表执行了一个then方法,2表示then方法执行了多次。
主要执行逻辑:
- 主体函数方法执行,一般是一个异步方法,执行完毕后若成功将state设置为1,失败设置为2;
- 检查defferedState值是否为0,若是0则等待then执行,不是则执行then中记录的回调。
- then方法执行,先设置一个Promise实例,并将其返回;将成功回调和失败回调记录下来,将defferedState设置为1。
- 检查state是否为0,为0则等待异步请求返回,1则执行成功方法,2执行失败方法。
- 将执行的回调返回值,用resolve的方式赋值给then中返回的Promise。
招聘!!!
字节跳动互娱基础架构团队招人啦!北京、深圳、杭州都有岗位!
我们是谁
字节成立最早的前端架构团队,目前规模最大,做的最专业,手里直接有大几百人的前端业务团队,产品 DAU 上亿级别,每天不用和 PM、UI 撕逼,有良好的技术氛围,业界大牛云集,团队成员都能获得相对好的技术成长。
平时工作
负责抖音、抖音火山版、直播等业务大规模复杂业务场景的前端架构设计、实现和优化
- 负责PC、H5、Hybrid、App Native、BFF、RPC等一种或几种技术场景的架构;
- 制定开发规范,工程化体系搭建及优化,提升开发效率、质量和性能,保障业务稳定运行;
- 发现现有流程及架构的问题,并持续进行优化;
- 解决业务遇到的技术痛点和难点;
- 跟进业内前沿技术,保证团队技术的先进性。
职位要求
- 本科及以上学历,计算机及相关专业;计算机基础扎实,熟悉数据结构、网络等;
- 有一定的架构和方案设计能力及经验,具备一定的方案沟通和推动能力;
- 对后端技术有一定了解,熟悉一门后端语言(java/go等);
- 对前端工程化(例如构建方面:webpack、rollup等)、Nodejs、渲染框架(例如react或vue等)、中后台搭建系统等至少其一有一定深度的实践经验者优先;
- 有大型网站架构经验者优先;有较高的技术热情和积极性者优先。
加分项
- 参与或主导过优秀的开源项目;
- 有优秀的技术博文、博客。
有意者可以添加我微信说明来意: