手写一个简易的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) } } 代码地址可查看:这里

本文章由javascript技术分享原创和收集

发表评论 (审核通过后显示评论):