-
Notifications
You must be signed in to change notification settings - Fork 0
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
从零扒一扒 promise #38
Labels
Comments
你能手写一个 Promise 吗?想要手写一个 Promise 则需要了解 Promise/A+ 规范并遵循这个规范。 简易版(入门)我们是这样来使用 promise: const p1 = new Promise((resolve, reject) => {
setTimeout(() => {resolve('success');}, 1000);
})
const p2 = p1.then(val => {
console.log(val)
}) 通过 Promise/A+ 规范和对其 API 的熟知,实现一个 Promise 的具体思路是:
一个简易版的 Promise 如下: // 声明一下三种状态值
const PENDING = 'PENDING';
const FULFILLED = 'FULFILLED';
const REJECTED = 'REJECTED';
class Promise {
constructor(executor) {
this.status = PENDING;
this.value = undefined;
this.reason = undefined;
// 通过发布订阅来执行 then
this.onResolvedCallbacks = [];
this.onRejectedCallbacks= [];
let resolve = (value) => {
if(this.status === PENDING) {
this.status = FULFILLED;
this.value = value;
this.onResolvedCallbacks.forEach(fn => fn());
}
}
let reject = (reason) => {
if(this.status === PENDING) {
this.status = REJECTED;
this.reason = reason;
this.onRejectedCallbacks.forEach(fn => fn());
}
}
try {
executor(resolve, reject)
} catch (error) {
reject(error)
}
}
then(onFulfilled, onRejected) {
// 兼容一下 executor 同步使用的情况
this.status === FULFILLED && onFulfilled(this.value);
this.status === REJECTED && onRejected(this.reason);
if (this.status === PENDING) {
// 这里通过发布订阅的方式,将 onFulfilled 和 onRejected 函数存放到具体状态的 callback 中
this.onResolvedCallbacks.push(() => {
onFulfilled(this.value)
});
this.onRejectedCallbacks.push(()=> {
onRejected(this.reason);
})
}
}
} 链式调用链式调用即当 Promise 的 then 函数中 return 了一个值,不管是什么值,我们都能在下一个 then 中获取到,这就是所谓的then 的链式调用。 回到第一个例子,我们改造一下使用链式写法: const p1 = new Promise((resolve, reject) => {
setTimeout(() => {resolve('success');}, 1000);
})
const p2 = p1.then(val => {
console.log('p2 success:', val);
// 或者 return 一个 promise/非 promise 的值
throw new Error('失败了')
}).then(
data => {
console.log('success again', data);
},
ex => {
console.log('fail again', data);
}
); 结合 Promise/A+ 规范,咱们需要做到:
大致的伪代码如下: // status ...
const resolvePromise = (newPromise, ret, resolve, reject) => {
// 如果是非基础类型的数据,另外判断处理
if ((typeof ret === 'object' && ret != null) || typeof ret === 'function') {
try {
// 如果上一个 then 的参数方法返回的是一个 promise,则等待 promise 执行完毕,再暴露执行结果
let then = ret.then;
if (typeof then === 'function') {
// 返回的 promise 执行后,将成功或者失败的结果暴露给当前 then 的 fulfilled or rejected
then.call(ret, succ => {
resolve(succ);
}, fail => {
reject(fail);
});
} else {
// 如果是引用类型(非 promise 的对象),则直接 resolve
resolve(ret);
}
} catch (ex) {
reject(ex);
}
} else {
// 如果上一个 then 参数中的函数返回值 ret 是个基本类型的值则直接 resolve
resolve(ret)
}
}
class Promise {
constructor(executor) {
// this.xxxs ...
let resolve = (value) => {
if(this.status === PENDING) {
this.status = FULFILLED;
this.value = value;
this.onResolvedCallbacks.forEach(fn=>fn());
}
}
let reject = (reason) => {
if(this.status === PENDING) {
this.status = REJECTED;
this.reason = reason;
this.onRejectedCallbacks.forEach(fn=>fn());
}
}
try {
executor(resolve, reject)
} catch (error) {
reject(error)
}
}
then(onFulfilled, onRejected) {
// 兼容 onFufilled,onRejected 不传值
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : () => {};
onRejected = typeof onRejected === 'function' ? onRejected : err => { throw err };
// then 最终返回一个新的 promise 实例
let newPromise = new Promise((resolve, reject) => {
// ...
if (this.status === PENDING) {
this.onResolvedCallbacks.push(() => {
setTimeout(() => {
try {
let ret = onFulfilled(this.value);
resolvePromise(newPromise, ret, resolve, reject);
} catch (e) {
reject(e)
}
}, 0);
});
this.onRejectedCallbacks.push(()=> {
setTimeout(() => {
try {
let ret = onRejected(this.reason);
resolvePromise(newPromise, ret, resolve, reject)
} catch (e) {
reject(e)
}
}, 0);
});
}
});
return newPromise;
}
}
静态方法常用的静态方法有 Promise.resolve(),Promise.reject(),可实现如下: static resolve/*or reject**/(data) {
return new Promise((resolve, reject) => {
resolve(data); // or reject(data);
})
} Promise.all实现思路是:
Promise.all = function(promises) {
return new Promise((resolve, reject) => {
let resultArr = [];
let orderIndex = 0;
const processResultByKey = (value, index) => {
resultArr[index] = value;
if (++orderIndex === promises.length) {
resolve(resultArr);
}
}
for (let i = 0; i < promises.length; i++) {
let value = promises[i];
if (value && typeof value.then === 'function') {
value.then((value) => {
processResultByKey(value, i);
}, reject);
} else { // 如果并发的数组并不是一个 promise,则直接加到记录结果的数组中即可
processResultByKey(value, i);
}
}
});
} Promise.racePromise.race = function(promises) {
return new Promise((resolve, reject) => {
for (let i = 0; i < promises.length; i++) {
let val = promises[i];
if (val && typeof val.then === 'function') {
val.then(resolve, reject);
} else {
resolve(val)
}
}
});
}
|
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
从零扒一扒 promise
在开发过程中,很多时候都在熟练的使用 Promise/A+ 规范,然而有时在使用的时候发现并不是很了解它的底层实现,下面扒一扒它的实现。
Promises/A+规范
术语
异步回调
Promise 解决的就是异步任务处理问题,简单举例如下(假设有一个异步任务 asyncJob1,它执行完成后要执行 asyncJob2):
这样做的问题是 callback 的控制权在 asyncJob1 里面了,并且若有多个异步任务将会有回调地狱的问题,如:
控制反转
稍稍把代码修改一下:
那么调用的方式就是:
写代码的时候,「同步」的写法语义更容易理解,即”干完某件事后,再处理另外一件事”,通过 then 方法来实现链式调用:
可按照如下例子来调用,这样看起来就更有序了。
带着上面的思路,接下来实现一个简版的 Promise。
Promise simple demo
上面的例子,都是函数执行完成后同步执行回调,看下面的例子:
于是上面的回调实现就可以变成:
Promise 支持多个回调
有时在某件事完成之后,可以同时做其他的多件事情,为此修改 Promise,增加回调队列:
于是在 job1 后可以添加多个回调
这样之后可能还不够,因为如果另外一个回调是异步处理的话,可能就没法得到结果了,比如:
可以在 then 中增加一个判断,如果已经 resolve 过了,则直接执行回调:这样处理后上面的'done2'就可以输出了
Promise 作用域安全性
以上的 Promise 返回后,外部可以直接访问 then、resolve 这两个方法,然而外部应该只关心 then,resolve 方法不应该暴露出去,防止外部调用 resolve 修改了 Promise 的状态。代码修整如下:
以上只列出了修改的代码,可以看出这个改动很小,其实就是给 then 封装多了一层,调用的方式就变成如下:
Promise 链式调用
截到目前为止,promise 原型还不能实现链式调用,比如这样调用的话,第二个 then 就会报错
链式调用是promise很重要的特性,为了实现链式调用,我们要实现:
先来看看代码实现:
执行以下代码,我们能得到:
we are all done! monkey with job1 with job2
的输出Promise 错误分支
以上的 Promise 都是只有成功的 resolve 调用,在使用的 Promise 都能接受 2 个回调:resolve、reject。
为了实现可以 reject,需要引入一个 promise 的状态,记录它是被 resolve 还是 reject 过。
为了简单起见,reject的代码和resolve差不多,可以抽取一下减少多余的代码。
Promise 融入异步
在上面的所有调用中,resolve 或 reject 里的回调调用都是同步的,这取决于回调的实现。如果回调本身是同步的,就可能会出问题。
比如按上面的 promise 的代码,把 job 的调用中的 setTimeout 去掉,就会得不到结果。
调用时机
resolve 和 reject 只有在执行环境堆栈仅包含平台代码时才可被调用 注1
注1 这里的平台代码指的是引擎、环境以及 promise 的实施代码。实践中要确保 resolve 和 reject 方法异步执行,且应该在 then 方法被调用的那一轮事件循环之后的新执行栈中执行。
这个事件队列可以采用“宏任务(macro - task)”机制或者“微任务(micro - task)”机制来实现。
由于 promise 的实施代码本身就是平台代码(译者注:即都是 JavaScript),故代码自身在处理在处理程序时可能已经包含一个任务调度队列。
所以我们要确保这些调用都是异步的,这里只是简单地用 setTimeout 来示意处理,这样之后像上面的调用也有结果输出了。
以上仅仅是简版的 Promise,离我们平常用的promise还差很远,仅仅给自己带来的一些思考。
The text was updated successfully, but these errors were encountered: