We read every piece of feedback, and take your input very seriously.
To see all available qualifiers, see our documentation.
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,就要遵循 Promise/A+ 规范,业界所有 Promise的类库都遵循这个规范
Promise
本篇文章写如何手写promise及其周边方法,每个方法从“定义->案例->实现”的思路展开
“定义->案例->实现”
定义
案例
实现
阅读本文前建议熟悉阮一峰老师的 ECMAScript 6 入门promise章节,熟悉promise的语法
本文提纲如下,基本覆盖到promise面试考点
promise
resolve
reject
then
resolved
rejected
const PENDING = 'pending' const RESOLVED = 'resolved' const REJECTED = 'rejected' class Promise { constructor(executor) { this.status = PENDING this.value = undefined // 成功的值 this.reason = undefined // 失败原因 // 成功函数 let resolve = (value) => { if (this.status === PENDING) { this.value = value this.status = RESOLVED this.onFulfilledCallbacks.forEach(fn => fn()); } } // 失败函数 let reject = (reason) => { if (this.status === PENDING) { this.reason = reason this.status = REJECTED this.onRejectCallbacks.forEach(fn => fn()) } } try { executor(resolve, reject) // 默认执行器立即执行 } catch (e) { reject(e) // 如果立即执行函数发生错误等价于调用失败函数 } } then (onFulfilled, onReject) { // 同步 if (this.status === RESOLVED) { onFulfilled(this.value) } if (this.status === REJECTED) { onReject(this.reason) } // 异步订阅 if (this.status === PENDING) { this.onFulfilledCallbacks.push(() => onFulfilled(this.value)) this.onRejectCallbacks.push(() => onReject(this.reason)) } } } module.exports = Promise
现象
在 executor()中传入一个异步操作
executor()
异步操作
const promise = new Promise((resolve, reject) => { // 异步的情况 setTimeout(() => { reject(1) }, 1000) }) promise.then(data => { console.log(data) }, err => { console.log('err', err) }) // 输出结果 err 1
实现 promise调用then方法时,当前的promise并没有成功,一直处于pending状态,所以如果当调用 then方法时,当前状态是pending,需要先将成功和失败的回调分别存放起来,在executor()的异步任务被执行时,触发resolve或reject,依次调用成功或失败的回调
pending
先将成功和失败的回调分别存放起来
触发resolve或reject,依次调用成功或失败的回调
class Promise { constructor(executor) { ... // 处理异步 this.onFulfilledCallbacks = [] this.onRejectCallbacks = [] // 成功函数 let resolve = (value) => { if (this.status === PENDING) { this.value = value this.status = RESOLVED ++this.onFulfilledCallbacks.forEach(fn => fn()); } } // 失败函数 let reject = (reason) => { if (this.status === PENDING) { this.reason = reason this.status = REJECTED ++this.onRejectCallbacks.forEach(fn => fn()) } } try { executor(resolve, reject) // 默认执行器立即执行 } catch (e) { reject(e) // 如果立即执行函数发生错误等价于调用失败函数 } } then (onFulfilled, onReject) { ... // 异步订阅 if (this.status === PENDING) { this.onFulfilledCallbacks.push(() => onFulfilled(this.value)) this.onRejectCallbacks.push(() => onReject(this.reason)) } } }
then方法返回的是一个新的Promise实例(注意,不是原来那个Promise实例)。因此可以采用链式写法,即then方法后面再调用另一个then方法 现象
下面的代码使用then方法,依次指定了两个回调函数。第一个回调函数完成以后,会将返回结果作为参数,传入第二个回调函数
const p = new Promise((resolve, reject) => { resolve(1001) }) p.then(data => { console.log(data) }, err => { console.log('err', err) }).then(data => { console.log(data) }, err => { console.log(err) }) // 输出结果 // 1001 // undefined
定义一个新的promise实例 promise2,并返回
promise2
then函数的返回值可能值一个普通值,也可能是一个对象,因此需要根据x的类型去处理then,引入resolvePromise方法统一处理,具体可看下一节2.2
普通值
一个对象
x
resolvePromise
为什么要用setTimeout:利用eventLoop,宏任务,代码块延迟执行,等new完promise2,不然resolvePromise(promise2, x, resolve, reject)中取不到promise2会报错
setTimeout
eventLoop
new完promise2
resolvePromise(promise2, x, resolve, reject)
取不到promise2会报错
then (onFulfilled, onReject) { let promise2 = new Promise((resolve, reject) => { // 同步 if (this.status === RESOLVED) { // 写setTimeout,利用eventLoop,宏任务,代码块延迟执行,等new完promise2 setTimeout(() => { try { let x = onFulfilled(this.value) resolvePromise(promise2, x, resolve, reject) } catch (e) { reject(e) } }, 0) } if (this.status === REJECTED) { setTimeout(() => { try { let x = onReject(this.reason) resolvePromise(promise2, x, resolve, reject) } catch (e) { reject(e) } }, 0) } // 异步订阅 if (this.status === PENDING) { this.onFulfilledCallbacks.push(() => { setTimeout(() => { try { let x = onFulfilled(this.value) resolvePromise(promise2, x, resolve, reject) } catch (e) { reject(e) } }, 0) }) this.onRejectCallbacks.push(() => { setTimeout(() => { try { let x = onReject(this.reason) resolvePromise(promise2, x, resolve, reject) } catch (e) { reject(e) } }, 0) }) } }) return promise2 }
前一个回调函数,有可能返回的还是一个Promise对象(即有异步操作),这时后一个回调函数,就会等待该Promise对象的状态发生变化,才会被调用
如下代码,promise2的then函数返回值为promise2
输出结果是[TypeError: Chaining cycle detected for promise #<Promise>]
[TypeError: Chaining cycle detected for promise #<Promise>]
// x跟promise2不能是一个东西 const p = new Promise((resolve, reject) => { resolve() }) let promise2 = p.then(() => { return promise2 }) promise2.then(null, err => { console.log(err) })
可以比喻为,A等A买菜回来,这是不可能的,所以直接报错
const resolvePromise = (promise2, x, resolve, reject) => { if (promise2 === x) { // 判断x的值与promise是否是同一个,如果是同一个,就不用等待了,直接出错即可 return reject(new TypeError('Chaining cycle detected for promise #<Promise>')) } }
x可能的值
// then函数的返回值还是一个promise const p = new Promise((resolve, reject) => { resolve(100) }) p.then(data => { return new Promise((resolve, reject) => { setTimeout(() => { resolve('success') }, 100) }) }, err => { console.log(err) }).then(data => { console.log(data) }, err => { console.log(err) }) // 输出结果:success // then函数中的resolve还是promise const p = new Promise((resolve, reject) => { resolve(100) }) p.then(data => { return new Promise((resolve, reject) => { setTimeout(() => { resolve(new Promise((resolve, reject) => { setTimeout(() => { resolve('success') }, 0) })) }, 100) }) }, err => { console.log(err) }).then(data => { console.log(data) }, err => { console.log(err) }) // 输出结果:success
先判断x是否是object或者function
object
function
resolve成功函数y
reject失败函数r
成功函数y
resolvePromise(promise2, y, resolve, reject)
{then: 1}
reslove(x)
resolve(x)
const resolvePromise = (promise2, x, reslove, reject) => { ... if (typeof x === 'object' && x !== null || typeof x === 'function') { try { let then = x.then // 取then可能这个then属性是通过defineProperty来定义的,可能报错 if (typeof then === 'function') { // 当有then方法,则认为x是一个promise then.call(x, y => { // y可能还是一个promise值,递归,直到解析出来的值是一个普通值 resolvePromise(promise2, y, resolve, reject) // 采用promise的成功结果将值下传递 }, reject => { reject(x) // 采用失败结果将值向下传递 }) } else { resolve(x) // x是一个普通对象,比如{then: 1} } } catch (e) { reject(e) } } else { resolve(x) // x是一个普通值,直接成功即可 } }
then方法的第一个参数是resolved状态的回调函数,第二个参数是rejected状态的回调函数,它们都是可选的 现象
如下代码,p.then().then()省略参数
p.then().then()
reslove时,最后一个then的resolve输出data依旧可以获取到数据
data
p.then(data => {return data}).then(data => {return data})
reject时,最后一个then的reject输出err依旧可以获取数据
err
p.then(null, err => { throw err }).then(null, err => { throw err })
// 案例 // then函数中的resolve和reject是可选参数 const p = new Promise((resolve, reject) => { resolve(100) }) p.then().then().then(data => { console.log(data) }) // p.then(data => {return data}).then(data => {return data}).then(data => { // console.log(data) // }) // p.then(null, err => { throw err }).then(null, err => { throw err }).then(null, err => { // console.log('err', err) // }) // 输出结果: // resolbe 100 // reject err 100
then(onFulfilled, onReject){ // onFulfilled、onReject是可选参数 onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : data => data onReject = typeof onReject === 'function' ? onReject : err => { throw err } ... }
Promise/A+规范提供了一个专门的测试脚本,可以测试所编写的代码是否符合Promise/A+的规范
Promise/A+
step1 全局安装包promises-aplus-tests
npm i -g promises-aplus-tests
step2 在primes文件最下方写入以下内容
Promise.defer = Promise.deferred = function () { let dfd = {} dfd.promise = new Promise((resolve, reject) => { dfd.resolve = resolve dfd.reject = reject }) return dfd }
step3 执行命令检测
promises-aplus-tests promise/promise.js
可以看到检测全部通过
Promise.all()
p1
p2
p3
Promise.resolve
p
fulfilled
const p = Promise.all([p1, p2, p3]);
Promise.all([1, 2, 3, 4]).then(data => { console.log(data) }) // 输出 [ 1, 2, 3, 4 ] Promise.all([1, 2, new Promise((resolve) => { resolve(300) }), 4]).then(data => { console.log(data) }) // 输出 [ 1, 2, 300, 4 ]
const isPromise = (data) => { if (typeof data === 'object' && data !== null || typeof data === 'function') { if (typeof data.then === 'function') { return true } } return false } Promise.all = function (promiseArr) { return new Promise((resolve, reject) => { let arr = [] let index = 0 function processData (i, data) { arr[i] = data if (++index === promiseArr.length) { // 不能用arr.length === promiseArr.length;因为promiseArr中有异步promise的话,arr不会按照顺序被塞进返回值 resolve(arr) } } for (let i = 0; i < promiseArr.length; i++) { let current = promiseArr[i] if (isPromise(current)) { // 如果是promis,执行then current.then(data => { processData(i, data) }, err => { console.log(err) reject(err) }) } else { // 如果不是promise,直接返回 processData(i, current) } } }) }
将现有对象转为Promise对象
Promise对象
Promise.resolve = function (value) { if (value instanceof Promise) return value return new Promise(resolve => resolve(value)) }
会返回一个新的 Promise 实例,该实例的状态为rejected
Promise.reject = function (reason) { return new Promise(_, reject => reject(reason)) }
finally()
finally
finally本质上是then方法的特例
promise .finally(() => { // 语句 }); // 等同于 promise .then( result => { // 语句 return result; }, error => { // 语句 throw error; } );
const p = new Promise((resolve, reject) => { resolve(100) }) p.finally(() => { console.log('finally') }).then(data => { console.log(data) }, err => { console.log('err', err) }) // 输出结果 // finally // 100 const p = new Promise((resolve, reject) => { reject(100) }) p.finally(() => { console.log('finally') }).then(data => { console.log(data) }, err => { console.log('err', err) }) // 输出结果 // finally // err 100 const p = new Promise((resolve) => { resolve(100) }) p.finally(() => { console.log('finally') return new Promise((resolve) => { setTimeout(() => { resolve() }, 4000) }) }).then(data => { console.log(data) }) // 输出结果 // finally // 等四秒钟 // 100
Promise.prototype.finally = function(callback){ return this.then( data => Promise.resolve(callback()).then(() => data), err => Promise.resolve(callback()).then(() => {throw err} ) ) }
Promise.prototype.catch()方法是.then(null, rejection)或.then(undefined, rejection)的别名,用于指定发生错误时的回调函数 现象
Promise.prototype.catch()
.then(null, rejection)
.then(undefined, rejection)
const p = new Promise((_, resolve) => { throw 'err' }).catch((e) => { console.log(e) }) // 输出 err
Promise.prototype.catch = function (onRejected) { return this.then(null, onRejected); }
现象 注意,下面代码第二个例子输出的是300,而非1
Promise.race([1, new Promise((resolve) => { resolve(300) }), 2, 4]).then(data => { console.log(data) }) // 输出 1 Promise.race([new Promise((resolve) => { resolve(300) }), 1, 2, 4]).then(data => { console.log(data) }) // 输出 300
Promise.race = function (promiseArr) { return new Promise((resolve, reject) => { for (let i = 0; i < promiseArr.length; i++) { const current = promiseArr[i]; Promise.resolve(current).then(resolve, reject); } }); }
本篇文章所有的代码在github/zxyue25
The text was updated successfully, but these errors were encountered:
No branches or pull requests
0、前提
我们想要手写一个
Promise
,就要遵循 Promise/A+ 规范,业界所有Promise
的类库都遵循这个规范本篇文章写如何手写promise及其周边方法,每个方法从
“定义->案例->实现”
的思路展开定义
主要参考的阮一峰老师的 ECMAScript 6 入门promise章节案例
为最基础的自己手写的,保证简单易懂实现
结合 Promise/A+ 规范实现本文提纲如下,基本覆盖到
promise
面试考点1、基本封装
1.1 基本
定义
Promise
对象是一个构造函数,用来生成Promise
实例Promise
构造函数接受一个函数作为参数,该函数的两个参数分别是resolve
和reject
。它们是两个函数,由 JavaScript 引擎提供,不用自己部署resolve
函数的作用是,将Promise
对象的状态从“未完成”变为“成功”(即从 pending 变为 resolved),在异步操作成功时调用,并将异步操作的结果,作为参数传递出去reject
函数的作用是,将Promise
对象的状态从“未完成”变为“失败”(即从 pending 变为 rejected),在异步操作失败时调用,并将异步操作报出的错误,作为参数传递出去Promise
实例生成以后,可以用then
方法分别指定resolved
状态和rejected
状态的回调函数实现
1.2 promise参数入参是一个异步操作
现象
在
executor()
中传入一个异步操作
实现
promise
调用then
方法时,当前的promise
并没有成功,一直处于pending
状态,所以如果当调用then
方法时,当前状态是pending
,需要先将成功和失败的回调分别存放起来
,在executor()
的异步任务被执行时,触发resolve或reject,依次调用成功或失败的回调
2、Promise.prototype.then
2.1 then的链式调用
定义
下面的代码使用
then
方法,依次指定了两个回调函数。第一个回调函数完成以后,会将返回结果作为参数,传入第二个回调函数实现
定义一个新的promise实例
promise2
,并返回then
函数的返回值可能值一个普通值
,也可能是一个对象
,因此需要根据x
的类型去处理then
,引入resolvePromise
方法统一处理,具体可看下一节2.2为什么要用
setTimeout
:利用eventLoop
,宏任务,代码块延迟执行,等new完promise2
,不然resolvePromise(promise2, x, resolve, reject)
中取不到promise2会报错
2.2 then函数返回值类型
(1)x跟promise2不能只一个东西
现象
如下代码,promise2的then函数返回值为promise2
输出结果是
[TypeError: Chaining cycle detected for promise #<Promise>]
实现
可以比喻为,A等A买菜回来,这是不可能的,所以直接报错
(2)x返回值可能是promise也可能是普通值
x可能的值
现象
实现
先判断
x
是否是object
或者function
x
上的then
then
是否是function
then
,resolve成功函数y
,reject失败函数r
,成功函数y
可能还是一个promise
,递归执行resolvePromise(promise2, y, resolve, reject)
x
只是普通的对象,比如{then: 1}
,直接reslove(x)
x
是普通值,直接resolve(x)
(3)then的两个参数是可选的,如果不写默认resolve(data)
定义
如下代码,
p.then().then()
省略参数reslove时,最后一个
then
的resolve
输出data
依旧可以获取到数据p.then(data => {return data}).then(data => {return data})
reject时,最后一个
then
的reject
输出err
依旧可以获取数据p.then(null, err => { throw err }).then(null, err => { throw err })
实现
3、检测Promise是否符合规范
Promise/A+
规范提供了一个专门的测试脚本,可以测试所编写的代码是否符合Promise/A+的规范step1 全局安装包promises-aplus-tests
step2 在primes文件最下方写入以下内容
step3 执行命令检测
可以看到检测全部通过
4、Promise.all
定义
Promise.all()
方法用于将多个Promise实例,包装成一个新的Promise实例Promise.all()
方法接受一个数组作为参数,p1
、p2
、p3
都是 Promise 实例,如果不是,就会先调用下面讲到的Promise.resolve
方法,将参数转为 Promise 实例,再进一步处理p
的状态由p1
、p2
、p3
决定,分成两种情况p1
、p2
、p3
的状态都变成fulfilled
,p
的状态才会变成fulfilled
,此时p1
、p2
、p3
的返回值组成一个数组,传递给p
的回调函数。p1
、p2
、p3
之中有一个被rejected
,p
的状态就变成rejected
,此时第一个被reject
的实例的返回值,会传递给p
的回调函数现象
实现
5、Promise.resolve & Promise.reject
resolve
将现有对象转为
Promise对象
reject
会返回一个新的 Promise 实例,该实例的状态为
rejected
6、Promise.prototype.finally
定义
finally()
方法用于指定不管 Promise 对象最后状态如何,都会执行的操作finally
方法的回调函数不接受任何参数,这意味着没有办法知道,前面的 Promise 状态到底是fulfilled
还是rejected
。这表明,finally
方法里面的操作,应该是与状态无关的,不依赖于 Promise 的执行结果finally
本质上是then
方法的特例现象
实现
7、Promise.prototype.catch
Promise.prototype.catch()
方法是.then(null, rejection)
或.then(undefined, rejection)
的别名,用于指定发生错误时的回调函数现象
实现
8、Promise.race
现象
注意,下面代码第二个例子输出的是300,而非1
实现
写在最后
本篇文章所有的代码在github/zxyue25
参考
The text was updated successfully, but these errors were encountered: