观感度:🌟🌟🌟🌟🌟前端
口味:海底捞git
烹饪时间:15mingithub
本文已收录在前端食堂同名仓库Github github.com/Geekhyt,欢迎光临食堂,若是以为酒菜还算可口,赏个 Star 对食堂老板来讲是莫大的鼓励。
Promise/A+ 规范镇楼!web
若是你没读过 Promise/A+ 规范也不要紧,我帮你总结了以下三部分重点:segmentfault
不过建议看完本文后仍是要亲自去读一读,很少 bb,开始展现。设计模式
Promise 的三个状态分别是 pending
、fulfilled
和 rejected
。数组
pending
: 待定,Promise 的初始状态。在此状态下能够落定 (settled)
为 fulfilled
或 rejected
状态。fulfilled
: 兑现(解决),表示执行成功。Promise 被 resolve 后的状态,状态不可再改变,且有一个私有的值 value。rejected
: 拒绝,表示执行失败。Promise 被 reject 后的状态,状态不可再改变,且有一个私有的缘由 reason。注意:value 和 reason 也是不可变的,它们包含原始值或对象的不可修改的引用,默认值为 undefined
。promise
要求必须提供一个 then 方法来访问当前或最终的 value 或 reason。安全
promise.then(onFulfilled, onRejected)
1.then 方法接受两个函数做为参数,且参数可选。
2.若是可选参数不为函数时会被忽略。
3.两个函数都是异步执行,会放入事件队列等待下一轮 tick。
4.当调用 onFulfilled 函数时,会将当前 Promise 的 value 值做为参数传入。
5.当调用 onRejected 函数时,会将当前 Promise 的 reason 失败缘由做为参数传入。
6.then 函数的返回值为 Promise。
7.then 能够被同一个 Promise 屡次调用。
Promise 的解决过程是一个抽象操做,接收一个 Promise 和一个值 x。babel
针对 x 的不一样值处理如下几种状况:
1.x 等于 Promise
抛出 TypeError 错误,拒绝 Promise。
2.x 是 Promise 的实例
若是 x 处于待定状态,那么 Promise 继续等待直到 x 兑现或拒绝,不然根据 x 的状态兑现/拒绝 Promise。
3.x 是对象或函数
取出 x.then 并调用,调用时将 this 指向 x。将 then 回调函数中获得的结果 y 传入新的 Promise 解决过程当中,递归调用。
若是执行报错,则将以对应的失败缘由拒绝 Promise。
这种状况就是处理拥有 then() 函数的对象或函数,咱们也叫它 thenable
。
4.若是 x 不是对象或函数
以 x 做为值执行 Promise。
var PENDING = 'pending'; var FULFILLED = 'fulfilled'; var REJECTED = 'rejected';
建立 Promise 时须要传入 execute
回调函数,接收两个参数,这两个参数分别用来兑现和拒绝当前 Promise。
因此咱们须要定义 resolve()
和 reject()
函数。
初始状态为 PENDING
,在执行时可能会有返回值 value
,在拒绝时会有拒绝缘由 reason
。
同时须要注意,Promise 内部的异常不能直接抛出,须要进行异常捕获。
function Promise(execute) { var that = this; that.state = PENDING; function resolve(value) { if (that.state === PENDING) { that.state = FULFILLED; that.value = value; } } function reject(reason) { if (that.state === PENDING) { that.state = REJECTED; that.reason = reason; } } try { execute(resolve, reject); } catch (e) { reject(e); } }
then
方法用来注册当前 Promise 状态落定后的回调,每一个 Promise 实例都须要有它,显然要写到 Promise 的原型 prototype
上,而且 then() 函数接收两个回调函数做为参数,分别是 onFulfilled
和 onRejected
。
Promise.prototype.then = function(onFulfilled, onRejected) {}
根据上面第 2 条规则,若是可选参数不为函数时应该被忽略,咱们要对参数进行以下判断。
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : function(x) { return x; } onRejected = typeof onRejected === 'function' ? onRejected : function(e) { throw e; }
根据第 3 条规则,须要使用 setTimeout
延迟执行,模拟异步。
根据第 4 条、第 5 条规则,须要根据 Promise 的状态来执行对应的回调函数。
在 PENDING 状态时,须要等到状态落定才能调用。咱们能够将 onFulfilled 和 onRejected 函数存到 Promise 的属性 onFulfilledFn
和 onRejectedFn
中,
当状态改变时分别调用它们。
var that = this; var promise; if (that.state === FULFILLED) { setTimeout(function() { onFulfilled(that.value); }); } if (that.state === REJECTED) { setTimeout(function() { onRejected(that.reason); }); } if (that.state === PENDING) { that.onFulfilledFn = function() { onFulfilled(that.value); } that.onRejectedFn = function() { onRejected(that.reason); } }
根据第 6 条规则,then 函数的返回值为 Promise,咱们分别给每一个逻辑添加并返回一个 Promise。
同时,then 支持链式调用,咱们须要将 onFulfilledFn 和 onRejectedFn 改为数组。
var that = this; var promise; if (that.state === FULFILLED) { promise = new Promise(function(resolve, reject) { setTimeout(function() { try { onFulfilled(that.value); } catch (reason) { reject(reason); } }); }); } if (that.state === REJECTED) { promise = new Promise(function(resolve, reject) { setTimeout(function() { try { onRejected(that.reason); } catch (reason) { reject(reason); } }); }); } if (that.state === PENDING) { promise = new Promise(function(resolve, reject) { that.onFulfilledFn.push(function() { try { onFulfilled(that.value); } catch (reason) { reject(reason); } }) that.onRejectedFn.push(function() { try { onRejected(that.reason); } catch (reason) { reject(reason); } }); }); }
与上面相对应的,再将 Promise 的构造函数相应的进行改造。
1.添加 onFulFilledFn 和 onRejectedFn 数组。
2.resolve() 和 reject() 函数改变状态时,须要异步调用数组中的函数,一样使用 setTimeout 来模拟异步。
function Promise(execute) { var that = this; that.state = PENDING; that.onFulfilledFn = []; that.onRejectedFn = []; function resolve(value) { setTimeout(function() { if (that.state === PENDING) { that.state = FULFILLED; that.value = value; that.onFulfilledFn.forEach(function(fn) { fn(that.value); }) } }) } function reject(reason) { setTimeout(function() { if (that.state === PENDING) { that.state = REJECTED; that.reason = reason; that.onRejectedFn.forEach(function(fn) { fn(that.reason); }) } }) } try { execute(resolve, reject); } catch (e) { reject(e); } }
Promise 解决过程分为如下几种状况,咱们须要分别进行处理:
1.x 等于 Promise TypeError 错误
此时至关于 Promise.then 以后 return 了本身,由于 then 会等待 return 后的 Promise,致使本身等待本身,一直处于等待。。
function resolvePromise(promise, x) { if (promise === x) { return reject(new TypeError('x 不能等于 promise')); } }
2.x 是 Promise 的实例
若是 x 处于待定状态,Promise 会继续等待直到 x 兑现或拒绝,不然根据 x 的状态兑现/拒绝 Promise。
咱们须要调用 Promise 在构造时的函数 resolve() 和 reject() 来改变 Promise 的状态。
function resolvePromise(promise, x, resolve, reject) { // ... if (x instanceof Promise) { if (x.state === FULFILLED) { resolve(x.value); } else if (x.state === REJECTED) { reject(x.reason); } else { x.then(function(y) { resolvePromise(promise, y, resolve, reject); }, reject); } } }
3.x 是对象或函数
取出 x.then 并调用,调用时将 this 指向 x,将 then 回调函数中获得的结果 y 传入新的 Promise 解决过程当中,递归调用。
若是执行报错,则将以对应的失败缘由拒绝 Promise。
x 多是一个 thenable 而非真正的 Promise。
须要设置一个变量 executed
避免重复调用。
function resolvePromise(promise, x, resolve, reject) { // ... if ((x !== null) && ((typeof x === 'object' || (typeof x === 'function'))) { var executed; try { var then = x.then; if (typeof then === 'function') { then.call(x, function(y) { if (executed) return; executed = true; return resolvePromise(promise, y, resolve, reject); }, function (e) { if (executed) return; executed = true; reject(e); }) } else { resolve(x); } } catch (e) { if (executed) return; executed = true; reject(e); } } }
4.直接将 x 做为值执行
function resolvePromise(promise, x, resolve, reject) { // ... resolve(x) }
// 为了支持测试,将模块导出 module.exports = { deferred() { var resolve; var reject; var promise = new Promise(function (res, rej) { resolve = res; reject = rej; }) return { promise, resolve, reject } } }
咱们能够选用这款测试工具对咱们写的 Promise 进行测试 Promise/A+ 测试工具: promises-aplus-tests。
目前支持 827 个测试用例,咱们只须要在导出模块的时候遵循 CommonJS 规范,按照要求导出对应的函数便可。
Promise.resolve()
能够实例化一个解决(fulfilled) 的 Promise。
Promise.resolve = function(value) { if (value instanceof Promise) { return value; } return new Promise(function(resolve, reject) { resolve(value); }); }
Promise.reject()
能够实例化一个 rejected 的 Promise 并抛出一个异步错误(这个错误不能经过try/catch捕获,只能经过拒绝处理程序捕获)
Promise.reject = function(reason) { return new Promise(function(resolve, reject) { reject(reason); }); }
Promise.prototype.catch()
方法用于给 Promise 添加拒绝时的回调函数。
Promise.prototype.catch = function(onRejected) { return this.then(null, onRejected); }
Promise.prototype.finally()
方法用于给 Promise 添加一个无论最终状态如何都会执行的操做。
Promise.prototype.finally = function(fn) { return this.then(function(value) { return Promise.resolve(value).then(function() { return value; }); }, function(error) { return Promise.resolve(reason).then(function() { throw error; }); }); }
Promise.all()
方法会将多个 Promise 实例组合成一个新的 Promise 实例。
组合后的 Promise 实例只有当每一个包含的 Promise 实例都解决(fulfilled)后才解决(fulfilled),若是有一个包含的 Promise 实例拒绝(rejected)了,则合成的 Promise 也会拒绝(rejected)。
两个注意点:
Promise.all = function(promiseArr) { return new Promise(function(resolve, reject) { const length = promiseArr.length; const result = []; let count = 0; if (length === 0) { return resolve(result); } for (let item of promiseArr) { Promise.resolve(item).then(function(data) { result[count++] = data; if (count === length) { resolve(result); } }, function(reason) { reject(reason); }); } }); }
Promise.race()
一样返回一个合成的 Promise 实例,其会返回这一组中最早解决(fulfilled)或拒绝(rejected)的 Promise 实例的返回值。
Promise.race = function(promiseArr) { return new Promise(function(resolve, reject) { const length = promiseArr.length; if (length === 0) { return resolve(); } for (let item of promiseArr) { Promise.resolve(item).then(function(value) { return resolve(value); }, function(reason) { return reject(reason); }); } }); }
Promise.any() 至关于 Promise.all() 的反向操做
,一样返回一个合成的 Promise 实例,只要其中包含的任何一个 Promise 实例解决(fulfilled)了,合成的 Promise 就解决(fulfilled)。
只有当每一个包含的 Promise 都拒绝(rejected)了,合成的 Promise 才拒绝(rejected)。
Promise.any = function(promiseArr) { return new Promise(function(resolve, reject) { const length = promiseArr.length; const result = []; let count = 0; if (length === 0) { return resolve(result); } for (let item of promiseArr) { Promise.resolve(item).then((value) => { return resolve(value); }, (reason) => { result[count++] = reason; if (count === length) { reject(result); } }); } }); }
Promise.allSettled()
方法也是返回一个合成的 Promise,不过只有等到全部包含的每一个 Promise 实例都返回结果落定时,不论是解决(fulfilled)仍是拒绝(rejected),合成的 Promise 才会结束。一旦结束,状态老是 fulfilled。
其返回的是一个对象数组,每一个对象表示对应的 Promise 结果。
对于每一个结果对象,都有一个 status 字符串。若是它的值为 fulfilled,则结果对象上存在一个 value 。若是值为 rejected,则存在一个 reason 。
Promise.allSettled = function(promiseArr) { return new Promise(function(resolve) { const length = promiseArr.length; const result = []; let count = 0; if (length === 0) { return resolve(result); } else { for (let item of promiseArr) { Promise.resolve(item).then((value) => { result[count++] = { status: 'fulfilled', value: value }; if (count === length) { return resolve(result); } }, (reason) => { result[count++] = { status: 'rejected', reason: reason }; if (count === length) { return resolve(result); } }); } } }); } // 使用 Promise.finally 实现 Promise.allSettled = function(promises) { // 也可使用扩展运算符将 Iterator 转换成数组 // const promiseArr = [...promises] const promiseArr = Array.from(promises) return new Promise(resolve => { const result = [] const len = promiseArr.length; let count = len; if (len === 0) { return resolve(result); } for (let i = 0; i < len; i++) { promiseArr[i].then((value) => { result[i] = { status: 'fulfilled', value: value }; }, (reason) => { result[i] = { status: 'rejected', reason: reason }; }).finally(() => { if (!--count) { resolve(result); } }); } }); } // 使用 Promise.all 实现 Promise.allSettled = function(promises) { // 也可使用扩展运算符将 Iterator 转换成数组 // const promiseArr = [...promises] const promiseArr = Array.from(promises) return Promise.all(promiseArr.map(p => Promise.resolve(p).then(res => { return { status: 'fulfilled', value: res } }, error => { return { status: 'rejected', reason: error } }))); };
先来简单回顾下 Generator 的使用:
function* webCanteenGenerator() { yield '店小二儿,给我切两斤牛肉来'; yield '再来十八碗酒'; return '好酒!这酒有力气!'; } var canteen = webCanteenGenerator(); canteen.next(); canteen.next(); canteen.next(); canteen.next(); // {value: "店小二儿,给我切两斤牛肉来", done: false} // {value: "再来十八碗酒", done: false} // {value: "好酒!这酒有力气!", done: true} // {value: undefined, done: true}
// 简易版 // 定义生成器函数,入参是任意集合 function webCanteenGenerator(list) { var index = 0; var len = list.length; return { // 定义 next 方法 // 记录每次遍历位置,实现闭包,借助自由变量作迭代过程当中的“游标” next: function() { var done = index >= len; // 若是索引尚未超出集合长度,done 为 false var value = !done ? list[index++] : undefined; // 若是 done 为 false,则能够继续取值 // 返回遍历是否完毕的状态和当前值 return { done: done, value: value } } } } var canteen = webCanteenGenerator(['道路千万条', '安全第一条', '行车不规范']); canteen.next(); canteen.next(); canteen.next(); // {done: false, value: "道路千万条"} // {done: false, value: "安全第一条"} // {done: false, value: "行车不规范"} // {done: true, value: undefined}
Generator 缺陷:
async 函数对 Generator 函数改进以下:
async/await 作的事情就是将 Generator 函数转换成 Promise,说白了,async 函数就是 Generator 函数的语法糖,await 命令就是内部 then 命令的语法糖。
const fetchData = (data) => new Promise((resolve) => setTimeout(resolve, 1000, data + 1)) const fetchResult = async function () { var result1 = await fetchData(1); var result2 = await fetchData(result1); var result3 = await fetchData(result2); console.log(result3); } fetchResult();
能够尝试经过 Babel 官网转换一下上述代码,能够看到其核心就是 _asyncToGenerator
方法。
咱们下面来实现它。
function asyncToGenerator(generatorFn) { // 将 Generator 函数包装成了一个新的匿名函数,调用这个匿名函数时返回一个 Promise return function() { // 生成迭代器,至关于执行 Generator 函数 // 如上面三碗不过岗例子中的 var canteen = webCanteenGenerator() var gen = generatorFn.apply(this, arguments); return new Promise(function(resolve, reject) { // 利用 Generator 分割代码片断,每个 yield 用 Promise 包裹起来 // 递归调用 Generator 函数对应的迭代器,当迭代器执行完成时执行当前的 Promise,失败时则拒绝 Promise function step(key, arg) { try { var info = gen[key](arg); var value = info.value; } catch (error) { reject(error); return; } if (info.done) { // 递归终止条件,完成了就 resolve resolve(value); } else { return Promise.resolve(value).then(function(value) { step('next', value); }, function(err) { step('throw', err); }); } } return step('next'); }); } }
好了,本文到这里就告一段落,若是上述代码你发现有问题的地方,能够在评论区留言,一块儿探讨学习。
1.看到这里了就点个赞支持下吧,你的赞是我创做的动力。
2.关注公众号前端食堂,你的前端食堂,记得按时吃饭!
3.本文已收录在前端食堂 github.com/Geekhyt,求个小星星,感谢Star。