手写一个简易的Promise
1. 简述 Promise
所谓 Promise,简单来说,就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果。
Promise 对异步调用进行封装,是一种异步编程的解决方案。
从语法上来说,Promise 是一个对象,从它可以获取异步操作的消息。
1.1 解决什么问题
有了 Promise 对象,就可以将异步操作以同步操作的流程表达出来,避免了层层嵌套的回调函数,即回调地狱。
1.2 优点
减少缩进
让回调函数变成了规范的链式写法,程序流程可以看得很清楚。
改写前:
f1( xxx , function f2(a){
f3( yyy , function f4(b){
f5( a + b , function f6(){})
})
})
改写后:
f1(xxx)
.then(f2) // f2 里面调用f3
.then(f4) // f4 里面调用f5,注意,f2 的输出作为 f4 的输入,即可将 a 传给 f4
.then(f6)
消灭 if (error)的写法
为多个回调函数中抛出的错误,统一指定处理方法。
而且,Promise 还有一个传统写法没有的好处:它的状态一旦改变,无论何时查询,都能得到这个状态。
1.3 用法
function fn(){
//new Promise 接受一个函数,返回一个Promise实例
return new Promise(( resolve, reject ) => {
resolve() // 成功时调用
reject() // 失败时调用
})
}
fn().then(success, fail).then(success2, fail2)
new Promise 接受一个函数,返回一个 Promise 实例
1.4 完整API
Promise是一个类
JS里类是特殊的函数
类属性:length(可以忽略)
永远是1,因为构造函数只接受一个参数
类方法:all / allSettled / race / reject/ resolve
对象属性:then / finally / catch
对象内部属性:state = pending / fulfilled / rejected
API 的规则是? Promise / A+规格文档 (JS 的 Promise的公开标准,中文翻译 笔者不保证其准确性)
1.5 其他
Promise 对象代表一个异步操作,有三种状态:pending(进行中)、fulfilled(已成功)和rejected(已失败)。
状态具有不受外界影响和不可逆2个特点。
不受外界影响
指只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态。这也是 Promise 这个名字的由来,它的英语意思就是“承诺”,表示其他手段无法改变。
不可逆
一旦状态改变,就不会再变化,会一直保持这个结果,称为 resolved(已定型),任何时候都可以得到这个结果。
2. 写之前的准备工作
2.1 创建目录
promise-demo
src
promise.ts
test
index.ts
2.2 测试驱动开发
按照规范文档写测试用例,
测试失败 -> 改代码 -> 测试成功 -> 加测试 -> ...
引入chai 和 sinon (测试框架)
普通的测试用 chai就够用了, sinon是用于测试函数的库。
chai 安装步骤
yarn global add ts-node mocha
//初始化
yarn init -y
yarn add chai mocha --dev
//添加TypeScript的类型声明文件
yarn add @types/chai @types/mocha --dev
//为了使用 yarn test 将以下两个安装到本地
yarn add --dev ts-node
yarn add --dev typescript
修改 package.json文件:
添加 test命令,这样就不用每次执行的时候都用 mocha -r ts-node/register test/**/*.ts命令,可以直接使用 yarn test 进行测试。
"scripts": {
"test": "mocha -r ts-node/register test/**/*.ts"
},
sinon 安装步骤
yarn add sinon sinon-chai --dev
yarn add @types/sinon @types/sinon-chai --dev
3. 具体实现
3.1 new Promise() 必须接受一个函数作为参数
测试代码:
import * as chai from "chai"
import Promise from "../src/promise"
const assert = chai.assert
describe("Promise", () => {
it("是一个类", () => {
assert.isFunction(Promise)
assert.isObject(Promise.prototype)
})
it("new Promise() 如果接受的不是一个函数就会报错", () => {
//assert.thow(fn)的作用:如果fn报错,控制台就不报错;如果fn不报错,控制台就报错。
//即,预测fn会报错
assert.throw(() => {
// @ts-ignore
new Promise()
})
assert.throw(() => {
//@ts-ignore
new Promise(1)
})
assert.throw(() => {
//@ts-ignore
new Promise(false)
})
})
})
assert.thow(fn)的作用:如果 fn报错,控制台就不报错;如果 fn不报错,控制台就报错。
即,预测 fn 会报错。
实现代码:
class Promise2 {
constructor(fn) {
if (typeof fn !== "function") {
throw new Error("只接受函数作为参数!")
}
}
}
export default Promise2
测试通过。
测试结果.PNG
3.2 new Promise(fn) 会生成一个对象,对象有 then 方法
test/index.ts
it("new Promise(fn)会生成一个对象,对象有 then 方法", () => {
const promise = new Promise(() => { })
assert.isObject(promise)
assert.isFunction(promise.then)
})
src/promise.ts
添加
then() {}
3.3 new Promise(fn)中的 fn 会立即执行
如何判断一个函数会立即执行? => sinon提供了简便的方法
使用sinon :
import * as sinon from "sinon"
import * as sinonChai from "sinon-chai"
chai.use(sinonChai)
it("new Promise(fn)中的 fn 会立即执行", () => {
//sinon提供了一个假的函数,这个假的函数知道自己有没被调用
let fn = sinon.fake()
new Promise(fn)
//如果这个函数被调用了,called 属性就为 true
assert(fn.called)
})
3.4 new Promise(fn)中的 fn 执行的时候必须接受 resolve 和 reject 两个函数
it("new Promise(fn)中的 fn 执行的时候必须接受 resolve 和 reject 两个函数", done => {
new Promise((resolve, reject) => {
assert.isFunction(resolve)
assert.isFunction(reject)
done()
})
})
关于done :因为有可能这两个语句根本没有执行,测试也会通过,所以使用 done 。用于保证 只有在运行 assert.isFunction(resolve); assert.isFunction(reject)之后才会结束这个测试用例。
3.5 promise.then(success)中的 success 会在 resolve 被调用的时候执行
it("promise.then(success)中的 success 会在 resolve 被调用的时候执行", done => {
let success = sinon.fake()
const promise = new Promise((resolve, reject) => {
assert.isFalse(success.called)
resolve()
//先等resolve里的success执行
setTimeout(() => {
assert.isTrue(success.called)
done()
})
})
promise.then(success)
})
then的时候,是先把 success 保存下来,等fn 调用 resolve的时候,resolve就会调用 success。(异步调用)
resolve需要先等一会,等 success先传入。
class Promise2 {
succeed = null
constructor(fn) {
if (typeof fn !== "function") {
throw new Error("只接受函数作为参数!")
}
fn(this.resolve.bind(this), this.reject.bind(this))
}
resolve() {
nextTick(() => {
this.succeed()
})
}
reject() {
}
then(succeed) {
this.succeed = succeed
}
}
promise.then(nulll,fail) 处的代码类似,不再说明。
3.6 参考文档写测试用例
promise 的 then 方法接收两个参数:
promise.then(onFulfilled, onRejected)
onFulfilled 和 onRejected 都是可选的参数,此外,如果参数不是函数,必须忽略
then(succeed?, fail?) {
if (typeof succeed === "function") {
this.succeed = succeed
}
if (typeof fail === "function") {
this.fail = fail
}
}
如果 onFulfilled 是函数:
此函数必须在 promise 完成(fulfilled)后被调用,并把 promise 的值(resolve接收的参数)作为onFulfilled它的第一个参数;
此函数不能被调用超过一次
resolve(result) {
if (this.state !== "pending") return;
this.state = "fulfilled"
nextTick(() => {
if (typeof this.succeed === "function") {
this.succeed(result)
}
})
}
onRejected 类似,不再说明。
then 可以在同一个 promise 里被多次调用
当 promise变为 fulfilled ,各个相应的 onFulfilled 回调 必须按照最原始的 then 顺序来执行
即传的是 0 1 2,调用的时候的顺序就是0 1 2
将目前的代码进行修改,目前的 then只保存一个 succeed 和 一个 fail,但实际上有可能会调用多次。
resolve(result) {
if (this.state !== "pending") return;
this.state = "fulfilled"
nextTick(() => {
//遍历callbacks,调用所有的handle[0]
this.callbacks.forEach(handle => {
if (typeof handle[0] === "function") {
handle[0].call(undefined, result)
}
})
})
}
then(succeed?, fail?) {
const handle = []
if (typeof succeed === "function") {
handle[0] = succeed
}
if (typeof fail === "function") {
handle[1] = fail
}
//把函数推到 callbacks 里面
this.callbacks.push(handle)
}
then必须返回一个promise (便于使用链式调用)
需要创建新的 Promise 实例来对第二个then 中接收的 succeed 和fail进行存储并执行
在原本的 resolve 和 reject 函数中,执行第二个 Promise 实例的resolve方法
参数传递
it("2.2.7 then必须返回一个promise", done => {
const promise = new Promise((resolve, reject) => {
resolve()
})
const promise2 = promise.then(() => "成功", () => { })
assert(promise2 instanceof Promise)
promise2.then(result => {
assert.equal(result, "成功")
done()
})
})
resolve(result) {
if (this.state !== "pending") return;
this.state = "fulfilled"
nextTick(() => {
//遍历callbacks,调用所有的handle[0]
this.callbacks.forEach(handle => {
let x
if (typeof handle[0] === "function") {
x = handle[0].call(undefined, result)
}
handle[2].resolve(x)
})
})
}
then(succeed?, fail?) {
const handle = []
if (typeof succeed === "function") {
handle[0] = succeed
}
if (typeof fail === "function") {
handle[1] = fail
}
handle[2] = new Promise2(() => { })
//把函数推到 callbacks 里面
this.callbacks.push(handle)
return handle[2]
}
再添加错误处理进行完善。
4. 手写Promise完整代码
class Promise2 {
state = "pending"
callbacks = []
constructor(fn) {
if (typeof fn !== "function") {
throw new Error("只接受函数作为参数!")
}
fn(this.resolve.bind(this), this.reject.bind(this))
}
resolve(result) {
if (this.state !== "pending") return;
this.state = "fulfilled"
nextTick(() => {
//遍历callbacks,调用所有的handle[0]
this.callbacks.forEach(handle => {
let x
if (typeof handle[0] === "function") {
try {
x = handle[0].call(undefined, result)
} catch (error) {
handle[2].reject(error)
}
}
handle[2].resolve(x)
})
})
}
reject(reason) {
if (this.state !== "pending") return;
this.state = "rejected"
nextTick(() => {
//遍历callbacks,调用所有的handle[1]
this.callbacks.forEach(handle => {
let x
if (typeof handle[1] === "function") {
try {
x = handle[1].call(undefined, reason)
} catch (error) {
handle[2].reject(error)
}
}
handle[2].resolve(x)
})
})
}
then(succeed?, fail?) {
const handle = []
if (typeof succeed === "function") {
handle[0] = succeed
}
if (typeof fail === "function") {
handle[1] = fail
}
handle[2] = new Promise2(() => { })
//把函数推到 callbacks 里面
this.callbacks.push(handle)
return handle[2]
}
}
export default Promise2
function nextTick(fn) {
if (process !== undefined && typeof process.nextTick === "function") {
return process.nextTick(fn)
} else {
var counter = 1
var observer = new MutationObserver(fn)
var textNode = document.createTextNode(String(counter))
observer.observe(textNode, {
characterData: true
});
counter = counter + 1
textNode.data = String(counter)
}
}
代码地址可查看:这里
发表评论 (审核通过后显示评论):