Promise 是 js 异步编程的一种解决方案,避免了 “回调地狱” 给编程带来的麻烦,在 ES6 中成为了标准,这篇文章重点不是叙述 Promise 的基本用法,而是从零开始,手写一版符合 Promise/A+ 规范的 Promise,若是想了解更多 Promise 的基本用法,能够看 异步发展流程 —— Promise 的基本使用 这篇文章。npm
咱们在使用 Promise 的时候实际上是使用 new
关键字建立了一个 Promise 的实例,其实 Promise 是一个类,即构造函数,下面来实现 Promise 构造函数。编程
Promise/A+ 规范的内容比较多,详情查看 https://promisesaplus.com/,咱们在实现 Promise 逻辑时会根据实现的部分介绍相关的 Promise/A+ 规范内容。数组
在 Promise/A+ 规范中规定:promise
executor
的执行器,即函数,在建立实例时该函数内部逻辑为同步,即当即执行;executor
执行时的参数分别为 resolve
和 reject
,一个为成功时执行的函数,一个为失败时执行的函数;executor
执行时,一旦出现错误当即调用 reject
函数,并设置错误信息给 reason
属性;pending
、fulfilled
和 rejected
,默认状态为 pending
;pending
到 fulfilled
或从 pending
到 rejected
,且不可逆;resolve
函数会使状态从 pending
变化到 fulfilled
并将参数存入实例的 value
属性中;reject
函数会使状态从 pending
变化到 rejected
并将错误信息存入实例的 reason
属性中。针对上面的 Promise/A+ 规范,Promise 构造函数代码实现以下:浏览器
// promise.js -- Promise 构造函数 function Promise(executor) { var self = this; self.status = "pending"; // 当前 Promise 实例的状态 self.value = undefined; // 当前 Promise 实例成功状态下的值 self.reason = undefined; // 当前 Promise 实例失败状态的错误信息 self.onFulfilledCallbacks = []; // 存储成功的回调函数的数组 self.onRejectedCallbacks = []; // 存储失败的回调函数的数组 // 成功的执行的函数 function resolve(value) { if (self.status === "pending") { self.status = "fulfilled"; self.value = value; // 每次调用 resolve 时,执行 onFulfilledCallbacks 内部存储的全部的函数(在实现 then 方法中详细说明) self.onFulfilledCallbacks.forEach(function(fn) { fn(); }); } } // 失败执行的函数 function reject(reason) { if (self.status === "pending") { self.status = "rejected"; self.reason = reason; // 每次调用 reject 时,执行 onRejectedCallbacks 内部存储的全部的函数(在实现 then 方法中详细说明) self.onRejectedCallbacks.forEach(function(fn) { fn(); }); } } // 调用执行器函数 try { executor(resolve, reject); } catch (e) { // 若是执行器执行时出现错误,直接调用失败的函数 reject(e); } } // 将本身的 Promise 导出 module.exports = Promise;
上面构造函数中的 resolve
和 reject
方法在执行的时候都进行了当前状态的判断,只有状态为 pending
时,才能执行判断内部逻辑,当两个函数有一个执行后,此时状态发生变化,再执行另外一个函数时就不会经过判断条件,即不会执行判断内部的逻辑,从而实现了两个函数只有一个执行判断内部逻辑的效果,使用以下:异步
// verify-promise.js -- 验证 promise.js 的代码 // 引入本身的 Promise 模块 // 由于都验证代码都写在 verify-promise.js 文件中,后面就再也不引入了 const Promise = require("./promise.js"); let p = new Promise((resolve, reject) => { // ...同步代码 resolve(); reject(); // 上面两个函数只有先执行的 resolve 生效 });
没有 Promise 以前在一个异步操做的回调函数中返回一个结果在输入给下一个异步操做,下一个异步操做结束后须要继续执行回调,就造成回调函数的嵌套,在 Promise 中,原来回调函数中的逻辑只须要调用当前 Promise 实例的 then
方法,并在 then
方法的回调中执行,改变了本来异步的书写方式。异步编程
在 then 方法中涉及到的 Promise/A+ 规范:函数
then
方法中有两个参数,都为函数,第一个参数为成功的回调 onFulfilled
,第二个参数为失败的回调 onRejected
;resolve
时,调用实例的 then
方法执行成功的回调 onFulfilled
,当 Promise 内部执行 reject
或执行出错时,调用实例的 then
方法执行错误的回调 onRejected
;then
方法须要支持异步,即若是 resovle
或 reject
执行为异步时,then
方法的回调 onFulfilled
或 onRejected
须要在后面执行;then
方法后须要返回一个新的 Promise 实例。若是 then
的回调中有返回值且是一个 Promise 实例,则该 Promise 实例执行后成功或失败的结果传递给下一个 Promise 实例的 then
方法 onFulfilled
(成功的回调)或 onRejected
(失败的回调)的参数,若是返回值不是 Promise 实例,直接将这个值传递给下一个 Promise 实例 then
方法回调的参数,then
的回调若是没有返回值至关于返回 undefined
;then
时,当任何一个 then
执行出错,链式调用下一个 then
时会执行错误的回调,错误的回调没有返回值至关于返回了 undefined
,再次链式调用 then
时会执行成功的回调;then
没有传递回调函数,或参数为 null
时,须要后面调用的 then
的回调函数来接收;executor
在 Promise 构造函数中执行时使用 try...catch...
捕获异常,可是内部执行的代码有多是异步的,因此须要在 then
方法中使用 try...catch...
再次捕获;then
方法中的回调为 micro-tasks
(微任务),回调内的代码应晚于同步代码执行,在浏览器内部调用微任务接口,咱们这里模拟使用宏任务代替。针对上面的 Promise/A+ 规范,then 方法代码实现以下:测试
// promise.js -- then 方法 Promise.prototype.then = function(onFulfilled, onRejected) { // 实现参数穿透 if(typeof onFulfilled !== "function") { onFulfilled = function (data) { return data; } } if(typeof onRejected !== "function") { onRejected = function (err) { throw err; } } // 返回新的 Promise,规范中规定这个 Promise 实例叫 promise2 var promise2 = new Promise(function (resolve, reject) { if (this.status === "fulfilled") { // 用宏任务替代模拟微任务,目的是使 `then` 的回调晚于同步代码执行 setTimeout(function () { try { // 捕获异步的异常 // onFulfilled 执行完返回值的处理,x 为成功回调的返回值 var x = onFulfilled(this.value); // 处理返回值单独封装一个方法 resolvePromise(promise2, x, resolve, reject); } catch (e) { reject(e); } }.bind(this), 0); } if (this.status === "rejected") { setTimeout(function () { try { // onRejected 执行完返回值的处理,x 为失败回调的返回值 var x = onRejected(this.reason); resolvePromise(promise2, x, resolve, reject); } catch (e) { reject(e); } }.bind(this), 0); } // 若是在 Promise 执行 resolve 或 renject 为异步 // 将 then 的执行程序存储在实例对应的 onFulfilledCallbacks 或 onRejectedCallbacks 中 if (this.status === "pending") { this.onFulfilledCallbacks.push(function() { setTimeout(function () { try { var x = onFulfilled(this.value); resolvePromise(promise2, x, resolve, reject); } catch (e) { reject(e); } }.bind(this), 0); }); this.onRejectedCallbacks.push(function() { setTimeout(function () { try { var x = onRejected(this.reason); resolvePromise(promise2, x, resolve, reject); } catch (e) { reject(e); } }.bind(this), 0); }); } }); return promise2; };
在处理 then
回调的返回值时,其实就是在处理该返回值与 then
方法在执行后返回的新 Promise 实例(即 promise2)之间的关系,由于不管 Promise 的执行器在执行 resolve
仍是 reject
是同步或是异步,都须要进行处理,因此咱们单独封装一个函数 resolvePromise
来处理。ui
resolvePromise 函数有四个参数:
then
执行后返回的 Promise 实例;then
的回调返回的结果;resolve
函数;reject
函数。在 resolvePromise 函数中涉及到的 Promise/A+ 规范:
then
后返回的新 Promise 实例称为 promise2
,将 then
回调返回的值称为 x
;promise2
和 x
为同一个对象,因为 x
要将执行成功或失败的结果传递 promise2
的 then
方法回调的参数,由于是同一个 Promise 实例,此时既不能成功也不能失败(本身不能等待本身完成),形成循环引用,这种状况下规定应该抛出一个类型错误来回绝;x
是一个对象或者函数且不是 null
,就去取 x
的 then
方法,若是 x
是对象,防止 x
是经过 Object.defineProperty
添加 then
属性,并添加 get
和 set
监听,若是在监听中抛出异常,须要被捕获到,x.then
是一个函数,就看成 x
是一个 Promise 实例,直接执行x
的 then
方法,执行成功就让 promise2
成功,执行失败就让 promise2
失败,若是 x.then
不是函数,则说明 x
为普通值,直接调用 promise2
的 resolve
方法将 x
传入,不知足条件说明该返回值就是一个普通值,直接执行 promise2
的 resolve
并将 x
做为参数传入;x
的 then
方法,回调中传入的参数仍是一个 Promise 实例,循环往复,须要递归 resolvePromise
进行解析;resolve
和 reject
的状况,应该声明一个标识变量 called
作判断来避免这种状况。针对上面的 Promise/A+ 规范,resolvePromise 函数代码实现以下:
// promise.js -- resolvePromise 方法 function resolvePromise(promise2, x, resolve, reject) { // 判断 x 和 promise2 是否是同一个函数 if (promise2 === x) { reject(new TypeError("循环引用")); } // x 是对象或者函数而且不是 null,若是不知足该条件说明 x 只是一个普通的值 if (x !== null && (typeof x === "object" || typeof x === "function")) { // 标识变量,防止递归内外层 resolve 和 reject 同时调用 // 针对 Promise,x 为普通值的时候能够放行 var called; // 为了捕获 Object.defineProperty 建立的 then 属性时添加监听所抛出的异常 try { var then = x.then; if (typeof then === "function") { // then 为一个方法,就看成 x 为一个 promise // 执行 then,第一个参数为 this(即 x),第二个参数为成功的回调,第三个参数为失败的回调 then.call(x, function (y) { if (called) return; called = true; // 若是 y 是 Promise 就继续递归解析 resolvePromise(promise2, y, resolve, reject); }, function (err) { if (called) return; called = true; reject(err); }); } else { // x 是一个普通对象,直接成功便可 resolve(x); } } catch(e) { if (called) return; called = true; reject(e); } } else { resolve(x); } }
上面咱们按照 Promise/A+ 规范实现了 Promise 的 then
方法,接下来针对上面的规范,用一些有针对行的案例来对 then
方法一一进行验证。
验证异步调用 resolve
或 reject
:
// 文件:verify-promise.js // 验证 promise.js 异步调用 resolve 或 reject let p = new Promise((resolve, reject) => { setTimeout(() => resolve(), 1000); }); p.then(() => console.log("执行了")); // 执行了
验证链式调用 then
返回 Promise 实例:
// 文件:verify-promise.js // 验证 promise.js then 回调返回 Promise 实例 let p1 = new Promise((resolve, reject) => resolve()); let p2 = new Promise((resolve, reject) => resolve("hello")); p1.then(() => p2).then(data => console.log(data)); // hello
验证链式调用 then
返回普通值:
// 文件:verify-promise.js // 验证 promise.js then 回调返回普通值 let p = new Promise((resolve, reject) => resolve()); p.then(() => "hello").then(data => console.log(data)); // hello
验证链式调用 then
中执行出错链式调用 then
执行错误的回调后,再次链式调用 then
:
// 文件:verify-promise.js // 验证 promise.js 链式调用 then 中执行出错链式调用 then 执行错误的回调后,再次链式调用 then let p = new Promise((resolve, reject) => resolve()); p.then(() => { throw new Error("error"); }).then(() => { console.log("success"); }, err => { console.log(err); }).then(() => { console.log("成功"); }, () => { console.log("失败"); }); // Error: error at p.then... // 成功
验证 then
的参数穿透:
// 文件:verify-promise.js // 验证 then 的参数穿透 let p1 = new Promise((resolve, reject) => resolve("ok")); let p2 = p1.then().then(data => { console.log(data); throw new Error("出错了"); }); p2.then().then(null, err => console.log(err)); // ok // 出错了
验证 then
方法是否晚于同步代码执行:
// 文件:verify-promise.js // 验证 then 方法是否晚于同步代码执行 let p = new Promise((resolve, reject) => { resolve(1); }); p.then(data => console.log(data)); console.log(2); // 2 // 1
验证循环引用:
// 文件:verify-promise.js // 验证 promise.js 循环引用 let p1 = new Promise((resolve, reject) => resolve()); // 让 p1 then 方法的回调返回本身 var p2 = p1.then(() => { return p2; }); p2.then(() => { console.log("成功"); }, err => { console.log(err); }); // TypeError: 循环引用 at resolvePromise...
验证 then
回调返回对象经过 Object.definePropertype
添加 then
属性并添加 get
监听,在触发监听时抛出异常:
// 文件:verify-promise.js // 验证 promise.js then 回调返回对象经过 Object.definePropertype 添加 then 和 get 监听,捕获异常 let obj = {}; Object.defineProperty(obj, "then", { get () { throw new Error(); } }); let p = new Promise((resolve, reject) => resolve()); p.then(() => { return obj; }).then(() => { console.log("成功"); }, () => { console.log("出错了"); }); // 出错了
验证每次执行 resolve
都传入 Promise 实例,须要将最终的执行结果传递给下一个 Promise 实例 then
的回调中:
// 文件:verify-promise.js // 验证 promise.js 每次执行 resolve 都传入 Promise 实例 let p = new Promise((resolve, reject) => resolve()); p.then(() => { return new Promise((resolve, reject) => { resolve(new Promise(resolve, reject) => { resolve(new Promise(resolve, reject) => { resolve(200); }); }); }); }).then(data => { console.log(data); }); // 200
// promise.js -- catch 方法 Promise.prototype.catch = function (onRejected) { return this.then(null, onRejected); }
catch
方法能够理解为是 then
方法的一个简写,只是参数中少了成功的回调,因此利用 Promise/A+ 规范中参数穿透的特性,很容易就实现了 catch
方法,catch
方法的真相就是这么的简单。
验证 catch
方法:
// 文件:verify-promise.js // 验证 promise.js 的 catch 方法 let p = new Promise((resolve, reject) => reject("err")); p.then().catch(err => { console.log(err); }).then(() => { console.log("成功了"); }); // err // 成功了
Promise.resolve
方法传入一个参数,并返回一个新的 Promise 实例,这个参数做为新 Promise 实例 then
方法成功回调的参数,在调用时感受直接成功了,实际上是直接执行了返回 Promise 实例的 resolve
。
// promise.js -- Promise.resolve 方法 Promise.resolve = function (val) { return new Promise(function (resolve, reject) { resolve(val); }); }
验证 Promise.resolve
方法:
// 文件:verify-promise.js // 验证 promise.js 的 Promise.resolve 方法 Promise.resolve("成功了").then(data => console.log(data)); // 成功了
Promise.reject
方法与 Promise.resolve
的实现思路相同,不一样的是,直接调用了返回新 Promise 实例的 reject
。
// promise.js -- Promise.reject 方法 Promise.reject = function (reason) { return new Promise(function (resolve, reject) { reject(reason); }); }
验证 Promise.reject
方法:
// 文件:verify-promise.js // 验证 promise.js 的 Promise.reject 方法 Promise.reject("失败了").then(err => console.log(err)); // 失败了
Promise.all
方法能够实现多个 Promise 实例的并行,返回值为一个新的 Promise 实例,当全部结果都为成功时,返回一个数组,该数组存储的为每个 Promise 实例的返回结果,这些 Promise 实例的返回顺序前后不肯定,可是返回值的数组内存储的返回结果是按照数组中 Promise 实例最初顺序进行排列的,返回的数组做为返回 Promise 实例成功回调的参数,当其中一个失败,直接返回错误信息,并做为返回 Promise 实例失败回调的参数。
// promise.js -- Promise.all 方法 Promise.all = function (promises) { return new Promise(function (resolve, reject) { // 存储返回值 var result = []; // 表明存入的个数,由于 Promise 为异步,不知道哪一个 Promise 先成功,不能用数组的长度来判断 var idx = 0; // 用来构建所有成功的返回值 function processData(index, data) { result[index] = data; // 将返回值存入数组 idx++; if (idx === promises.length) { resolve(result); } } for(var i = 0; i < promises.length; i++) { // 由于 Primise 为异步,保证 i 值是顺序传入 (function (i) { promises[i].then(function (data) { processData(i, data); }, reject); })(i); } }); }
验证 Promise.all
方法:
// 文件:verify-promise.js // 验证 promise.js 的 Promise.all 方法 let p1 = new Promise((resolve, reject) => setTimeout(() => resolve(1), 2000)); let p2 = new Promise((resolve, reject) => setTimeout(() => resolve(2), 1000)); Promise.all([p1, p2]).then(data => console.log(data)); // [1, 2] let p3 = new Promise((resolve, reject) => setTimeout(() => resolve(1), 2000)); let p4 = new Promise((resolve, reject) => setTimeout(() => reject(2), 1000)); Promise.all([p3, p4]).then(data => { console.log(data); }).catch(err => { console.log(err); }); // 2
Promise.race
方法与 Promise.all
相似,一样能够实现多个 Promise 实例的并行,一样返回值为一个新的 Promise 实例,参数一样为一个存储多个 Promise 实例的数组,区别是只要有一个 Promise 实例返回结果,不管成功或失败,则直接返回这个结果,并做为新 Promise 实例 then
方法中成功或失败的回调函数的参数。
// promise.js -- Promise.race 方法 Promise.race = function (promises) { return new Promise(function (resolve, reject) { for(var i = 0; i < promises.length; i++) { promises[i].then(resolve, reject); } }); }
验证 Promise.race
方法:
// 文件:verify-promise.js // 验证 promise.js 的 Promise.race 方法 let p1 = new Promise((resolve, reject) => setTimeout(() => resolve(1), 2000)); let p2 = new Promise((resolve, reject) => setTimeout(() => resolve(2), 1000)); Promise.race([p1, p2]).then(data => console.log(data)); // 2 let p3 = new Promise((resolve, reject) => setTimeout(() => resolve(1), 2000)); let p4 = new Promise((resolve, reject) => setTimeout(() => reject(2), 1000)); Promise.all([p3, p4]).then(data => { console.log(data); }).catch(err => { console.log(err); }); // 2
promises-aplus-test
是专门用来验证 Promise 代码是否符合 Promise/A+ 规范的包,须要经过 npm
下载。
npm install promises-aplus-test -g
测试方法:
promise.js
中写入测试代码;promises-aplus-test
+ fileName
。测试代码:
// promise.js -- 测试方法 Promise.derfer // Promise 语法糖 // 好处:解决 Promise 嵌套问题 // 坏处:错误处理不方便 Promise.derfer = Promise.deferred = function () { let dfd = {}; dfd.promise = new Promise((resolve, reject) => { dfd.resolve = resolve; dfd.reject = reject; }); return dfd; }
输入命令:
promises-aplus-test promise.js
执行上面命令后,会根据 Promise/A+ 规范一条一条进行极端的验证,当验证经过后会在窗口中这一条对应的执行项前打勾,验证不经过打叉,直到全部的规范都验证完毕。