文章内容输出来源:拉勾教育大前端高薪训练营
学习的时候才会发现诱惑实在是多,原本是周四就能理完这篇,没想到拖到了周日晚上,这会精力略有不足,话不多说,直接上干货好了。
今天是异步编程的高光时刻,来吧,先收藏一下,以免文章太长读不完以后找不到~
最早js语言就是运行在浏览器端的语言,目的是为了实现页面上的动态交互。实现页面交互的核心就是DOM操作,这就决定了它必须使用单线程模式,否则就会出现很复杂的线程同步问题。
假设在js中有多个线程一起工作,其中一个线程修改了这个DOM元素,同时另一个线程又删除了这个元素,此时浏览器就无法明确该以哪个工作线程为准。所以为了避免线程同步的问题,从一开始,js就设计成了单线程的工作模式。
所以,js执行环境中负责执行代码的线程只有一个。
一个人执行一个任务,如果有多个任务,那任务需要排队。让这个人一个一个去执行。
这种模式最大的优势就是更安全,更简单,缺点也很明确,就是如果中间有一个特别耗时的任务,其他的任务就要等待很长的时间,出现假死的情况。
console.log('foo');
for(let i = 0; i< 100000; i++) {
console.log('耗时结束');
}
console.log('等待耗时操作结束');
为了解决这种问题,js有两种任务执行的模式:同步模式(Synchronous)和异步模式(Asynchronous)
同步模式:指的是代码的任务依次执行,后一个任务必须等待前一个任务结束才能开始执行。程序的执行顺序和代码的编写顺序是完全一致的。在单线程模式下,大多数任务都会以同步模式执行,同步指的不是同时执行,而是排队执行。
console.log('global begin');
function bar () {
console.log('bar task');
}
function foo () {
console.log('foo task');
bar()
}
foo()
console.log('global end');
// global begin
// foo task
// bar task
// global end
js在执行引擎当中,维护了一个正在工作的工作表,或者说正在执行的工作表,里面会记录一些当前正在做的一些事情,当这个工作表中所有的任务全部执行完毕,被清空后,那这一轮的工作就算是结束了。
这是一个纯同步模式下的执行情况,整个执行过程非常符合阅读和思考逻辑,但是这种排队执行的机制存在一个很严重的问题,如果其中的某个任务或某行代码执行的时间过长,那么它后面的任务就会出现延迟,这种延迟被称为阻塞,这种阻塞会导致页面卡顿或卡死。
为了避免耗时函数让页面卡顿和假死,所以还有异步模式。
例如:ajax操作或nodejs中的大文件读写,都需要使用异步模式,避免卡死现象
异步模式不会去等待这个任务的结束才开始下一个任务,对于耗时操作,都是开启过后就立即往后执行下一个任务。耗时函数的后续逻辑会通过回调函数的方式定义。在内部,耗时任务完成过后就会自动执行传入的回调函数。
异步模式对于JavaScript语言非常重要,没有它就无法同时处理大量的耗时任务。对于开发者而言,单线程下面的异步最大的难点就是代码执行的顺序混乱。
console.log('global begin');
setTimeout(function timer1() {
console.log('timer1 invoke');
}, 1800)
setTimeout(function timer2() {
console.log('timer2 invoke');
setTimeout(function inner() {
console.log('inner invoke');
}, 1000)
}, 1000)
console.log('global end');
// global begin
// global end
// timer2 invoke
// timer1 invoke
// inner invoke
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fJiGly4R-1616946983267)(./image/JavaScript异步编程/02-async-mode01.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JQPfpHhO-1616946983277)(./image/JavaScript异步编程/02-async-mode02.png)]
js线程某个时刻发起了一个异步调用,它紧接着继续执行其他的任务,此时异步线程会单独执行异步任务,执行过后会将回调放到消息队列中,js主线程执行完任务过后会依次执行消息队列中的任务。这里强调,js是单线程的,浏览器不是单线程的,有一些API是有单独的线程去做的。
这里的同步和异步不是指写代码的方式,而是运行环境提供的API是以同步或异步模式的方式工作。
同步模式API的特点就是任务执行完代码才会继续往下走,例如:console.log
异步模式API的特点就是下达这个任务开启的指令之后代码就会继续执行,代码不会等待任务的结束,例如setTimeOut
回调函数:由调用者定义,交给执行者执行的函数
function foo (callback) {
setTimeout(function () {
callback()
}, 3000)
}
foo(function () {
console.log('这就是一个回调函数');
console.log('调用者定义这个函数,执行者执行这个函数');
console.log('其实就是调用者告诉执行者异步任务结束后应该做什么');
})
还有其他的一些实现异步的方式,例如:事件机制和发布订阅。这些也都是基于回调函数之上的变体。
虽然回调函数是所有异步编程方案的根基,但是如果我们直接使用传统回调方式去完成复杂的异步流程,就会无法避免大量的回调函数嵌套,导致回调地狱的问题。
// 回调地狱
$.get('/url1', function (data1) {
$.get('/url2', data1, function (data2) {
$.get('/url3', data2, function (data3) {
$.get('/url4', data3, function (data4) {
console.log(data4);
})
})
})
})
为了避免这个问题,CommonJS社区提出了Promise的规范,ES6中称为语言规范。
Promise是一个对象,用来表述一个异步任务执行之后是成功还是失败。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YbJnkQSb-1616946983279)(./image/JavaScript异步编程/04-promise-sample01.png)]
// Promise 基本示例
// promise其实是ECMAScript2015提供的全局类型,先构造一个promise实例(创建一个新的承诺)
const promise = new Promise(function (resolve, reject) {
// 需要接收一个函数参数(这里用于"兑现"承诺),着函数会在构造promise的过程中同步执行,这个函数接收两个函数参数resolve, reject
// resolve是将promise的状态修改为fulfilled,也就是成功,一般将异步操作的结果通过resolve的参数传递出去
// reject是将promise的状态修改为rejected,也就是失败,一般将异失败的原因通过reject的参数传递出去,表示为什么失败
// promise的状态一旦确定就不能再修改,所以只能调用两者中的一个
// resolve(100) // 承诺达成
reject(new Error('promise rejected')) // 承诺失败
})
promise.then(function (value) {
// 成功后的回调函数
console.log('resolved', value);
}, function (error) {
// 失败后的回调函数
console.log('rejected', error);
})
// 即便promise当中没有任何异步操作,then方法中所指定的回调函数仍然会进入到回调队列当中排队,也就是说,必须等待同步代码执行结束后才会执行
// 会先打印end
console.log('end');
返回resolve
const promise = new Promise(function (resolve, reject) {
resolve(100) // 承诺达成
})
promise.then(function (value) {
// 成功后的回调函数
console.log('resolved', value);
}, function (error) {
// 失败后的回调函数
console.log('rejected', error);
})
返回reject
const promise = new Promise(function (resolve, reject) {
reject(new Error('promise rejected')) // 承诺失败
})
promise.then(function (value) {
// 成功后的回调函数
console.log('resolved', value);
}, function (error) {
// 失败后的回调函数
console.log('rejected', error);
})
即便promise中没有任何的异步操作,then方法的回调函数依然会进入到事件队列中排队。
const promise = new Promise(function (resolve, reject) {
reject(new Error('promise rejected')) // 承诺失败
})
promise.then(function (value) {
// 成功后的回调函数
console.log('resolved', value);
}, function (error) {
// 失败后的回调函数
console.log('rejected', error);
})
// 会先打印end
console.log('end');
使用Promise去封装一个ajax的案例
// Promise 方式的 Ajax
function ajax (url) {
return new Promise(function (resolve, reject) {
var xhr = new XMLHttpRequest()
xhr.open('GET', url) // 设置请求方式及地址
xhr.responseType = 'json' // 响应类型为json,H5新特性
// onload也是H5新特性
xhr.onload = function () {
// 请求完成后
if (this.status === 200) {
// 请求成功
resolve(this.response)
} else {
// 请求失败
reject(new Error(this.statusText))
}
}
xhr.send()
})
}
ajax('/api/users.json').then(function (res) {
console.log(res);
}, function (error) {
console.log(error);
})
本质上也是使用回调函数的方式去定义异步任务结束后所需要执行的任务。这里的回调函数是通过then方法传递过去的。
ajax('/api/users.json').then(function onFulfilled (value) {
console.log('onFulfilled', value);
}, function onRejected (error) {
console.log('onRejected', error);
})
// Promise 常见误区
function ajax (url) {
return new Promise(function (resolve, reject) {
var xhr = new XMLHttpRequest()
xhr.open('GET', url)
xhr.responseType = 'json'
xhr.onload = function () {
if (this.status === 200) {
resolve(this.response)
} else {
reject(new Error(this.statusText))
}
}
xhr.send()
})
}
ajax('/api/urls.json').then(function (urls) {
ajax('/api/users.json').then(function (users) {
})
})
// Promise 链式调用
function ajax (url) {
return new Promise(function (resolve, reject) {
var xhr = new XMLHttpRequest()
xhr.open('GET', url)
xhr.responseType = 'json'
xhr.onload = function () {
if (this.status === 200) {
resolve(this.response)
} else {
reject(new Error(this.statusText))
}
}
xhr.send()
})
}
// const promise = ajax('/api/users.json')
// const promise2 = promise.then(
// function onFulfilled (value) {
// console.log('onFulfilled', value);
// },
// function onRejected (error) {
// console.log('onRejected', error);
// }
// )
// console.log(promise2 === promise);
// => false
const promise = ajax('/api/users.json')
// 不断链式调用then,每一个then方法,都在为上一个then方法返回的promise对象添加状态明确后的回调,这些promise会依次执行,这里添加的回调函数也是从前到后依次执行
// 也可以在then的回调当中手动返回一个promise对象,下一个then方法就是它的回调,这样就可以避免不必要的回调嵌套了
// 如果返回的不是promise,而是一个普通的值,这个值就会作为当前then方法返回的promise中的值,在下一个then方法中,接收到的参数实际就是这个值
// 如果没有返回值,默认返回undefined
ajax('/api/users.json')
.then(function (value) {
console.log(value);
return ajax('/api/users.json')
})
.then(function (value) {
console.log(value);
return 'foo'
})
.then(function (value) {
console.log(value);
})
.then(function (value) {
console.log(value);
})
// Promise 异常处理
function ajax (url) {
return new Promise(function (resolve, reject) {
// foo()
// throw new Error()
var xhr = new XMLHttpRequest()
xhr.open('GET', url)
xhr.responseType = 'json'
xhr.onload = function () {
if (this.status === 200) {
resolve(this.response)
} else {
reject(new Error(this.statusText))
}
}
xhr.send()
})
}
// onRejected回调为promise执行中的异常做处理
ajax('/api/users1.json').then(
function onFulfilled (value) {
console.log('onFulfilled', value);
},
function onRejected (error) {
console.log('onRejected', error);
}
)
promise中如果有异常,都会调用reject方法,还可以使用.catch()
使用.catch() 方法更为常见,因为更符合链式调用
ajax('/api/users1.json')
.then(
function onFulfilled (value) {
console.log('onFulfilled', value);
}
)
.catch(
function onRejected (error) {
console.log('onRejected', error);
}
)
.catch形式和前面then里面的第二个参数的形式,两者异常捕获的区别:
ajax('/api/users.json').then(
function onFulfilled (value) {
console.log('onFulfilled', value);
return ajax('/error-url')
},
function onRejected (error) {
console.log('onRejected', error);
}
)
// 每个then方法都会返回一个promise对象,catch实际是为上一个then返回的promise对象指定失败的回调,并不是给第一个指定的,因为promise执行时,报错会传递,所以在这里能捕获到第一个promise的异常
// 如果在then方法中返回了第二个promise,如果在这个promise执行过程中出现了异常,那使用then的第二个参数注册时失败回调,捕获不到第二个promise的异常,因为它只是给第一个promise对象注册了失败回调
ajax('/api/users.json')
.then(
function onFulfilled (value) {
console.log('onFulfilled', value);
}
)
.catch(
function onRejected (error) {
console.log('onRejected', error);
}
)
所以.catch()是给整个promise链条注册的一个失败回调。推荐使用!!!
还可以在全局对象上注册一个unhandledrejection事件,处理那些代码中没有被手动捕获的promise异常,当然并不推荐使用。
更合理的是:在代码中明确捕获每一个可能的异常,而不是丢给全局处理。
// 全局捕获 Promise 异常,类似于 window.onerror
window.addEventListener('unhandledrejection', event => {
const {
reason, promise } = event
console.log(reason, promise);
// reason => Promise 失败的原因,一般是一个错误对象
// promise => 出现异常的 Promise 对象
event.preventDefault()
}, false)
// node中全局捕获 Promise 异常
process.on('unhandledrejection', (reason, promise) => {
console.log(reason, promise);
// reason => Promise 失败的原因,一般是一个错误对象
// promise => 出现异常的 Promise 对象
}, false)
// 常用 Promise 静态方法
Promise.resolve('foo')
.then(function (value) {
console.log(value);
// => foo
})
new Promise(function (resolve, reject) {
resolve('foo')
})
var promise = ajax('/api/users.json')
var promise2 = Promise.resolve(promise)
console.log(promise === promise2);
// => true
Promise.resolve({
then: function (onFulfilled, onRejected) {
onFulfilled('foo')
}
})
.then(function (value) {
console.log(value);
// => foo
})
Promise.reject(new Error('rejected'))
.catch(function (error) {
console.log(error);
})
Promise.reject('anything')
.catch(function (error) {
console.log(error);
})
简单的并行执行,未判断执行是否完成
ajax('/api/users.json')
ajax('/api/posts.json')
Promise.all()
等待所有任务的结束
传统使用计数器,每次执行结束+1,当计数器的数量等于任务数时,所有任务执行结束。这种方法比较麻烦,而且还需要考虑出现异常的情况
- promise.all方法,将多个promise合并成一个promise统一管理。
- Promise.all接收一个数组,数组中每个元素都是都是一个Promise对象,可以看作是一个个的异步任务
- 这个方法返回一个全新的promise对象,当内部所有的promise完成后,这个返回的全新的promise才会完成
- 此时这个promise对象拿到的结果是一个数组,数组中每个元素都是对应任务的执行结果
- 只有所有任务都成,结果才会成功,只要有一个失败,那这个结果就是失败的
var promise = Promise.all([
ajax('/api/users.json'),
ajax('/api/posts.json')
])
promise.then(function (value) {
console.log(value);
})
ajax('/api/urls.json')
.then(value => {
const urls = Object.values(value)
const tasks = urls.map(url => ajax(url))
return Promise.all(tasks)
}).then (values => {
console.log(values);
})
Promise.race()
只会等待第一个任务的结束
const request = ajax('/api/posts.json')
const timeout = new Promise((resolve, reject) => {
setTimeout(() => reject(new Error('timeout')), 500)
})
Promise.race([
request,
timeout
]).then(values => {
console.log(values);
}).catch(error => {
console.log(error);
})
宏任务 VS 微任务
即便Promise中没有任何的异步操作,它的回调函数也仍然会进入到回调队列中排队,也就是说必须要等待当前所有的同步代码执行结束后才会执行Promise中的回调,但是这种说法并不严谨。
// 微任务
console.log('global start');
Promise.resolve()
.then(() => {
console.log('promise');
})
console.log('global end');
// => global start
// => global end
// => global promise
// 微任务
console.log('global start');
Promise.resolve()
.then(() => {
console.log('promise');
})
.then(() => {
console.log('promise 2');
})
.then(() => {
console.log('promise 3');
})
console.log('global end');
// => global start
// => global end
// => global promise
// => global promise 2
// => global promise 3
// 微任务
console.log('global start');
setTimeout(() => {
console.log('setTimeout');
}, 0)
Promise.resolve()
.then(() => {
console.log('promise');
})
.then(() => {
console.log('promise 2');
})
.then(() => {
console.log('promise 3');
})
console.log('global end');
// => global start
// => global end
// => global promise
// => global promise 2
// => global promise 3
// => global setTimeout
- 回调队列中的任务称为宏任务,宏任务执行过程中可以临时加上一些额外的需求,对于这些额外的需求,可以选择作为一个新的宏任务进到队列中排队,也可以作为当前任务的“微任务”,直接在当前任务结束过后立即执行
- Promise的回调会作为微任务执行,会在本轮调用结束的末尾自动执行
- setTimeout会以宏任务的形式进到队列的末尾
- 所以会先打印promise,再打印setTimeout
微任务是为了提高应用整体的响应能力,目前绝大多数异步调用都是作为宏任务执行
Promise && MutationObserver 和 node 中的 process.nextTick会作为微任务执行在本轮调用结束的末尾自动执行
Promise vs. Callback
// Callback hell
ajax('/api/url1', (error, value) => {
ajax('/api/url2', (error, value) => {
ajax('/api/url3', (error, value) => {
ajax('/api/url4', (error, value) => {
})
})
})
})
// Promise chain
ajax('/api/users.json')
.then(function (value) {
console.log(value);
return ajax('/api/users.json')
})
.then(function (value) {
console.log(value);
return 'foo'
})
.then(function (value) {
console.log(value);
})
.then(function (value) {
console.log(value);
})
Promise的链式调用依然会有很多的回调函数,虽然它们之前没有相互嵌套,但是还是没有传统的同步代码的可读性好
如果有类似下面这种代码,就比较简洁易读了
// sync mode code
try {
const value1 = ajax('/api/url1')
console.log(value1);
const value2 = ajax('/api/url2')
console.log(value2);
const value3 = ajax('/api/url3')
console.log(value3);
const value4 = ajax('/api/url4')
console.log(value4);
} catch (e) {
console.log(e);
}
ES2015提供的Generator(生成器函数)
相比于普通函数,Generator函数多了一个 *
function* foo () {
console.log('foo');
try {
const res = yield 'foo'
console.log(res);
} catch (e) {
console.log(e);
}
}
const generator = foo()
const result = generator.next()
console.log(result);
// generator.next('bar')
generator.throw(new Error('Generator error'))
- 调用Generator函数并不会立即执行这个函数,而是得到一个Generator对象,只有手动调用.next方法,这个函数的函数体才会开始执行
- 在函数内部可以随时使用yield关键词向外返回一个值,在next方法返回对象当中拿到这个值
- 在返回的对象中还有done属性,表示这个Generator是否已经全部执行结束
- yield关键词并不会像return一样立即结束这个函数执行,它只是暂停执行,直到外界下一次调用Generator对象的.next方法时,会继续从yield这个位置往下执行
- 另外调用Generator对象的.next方法时,传入了参数,传入的参数会作为yield语句的返回值,也就是说在yield的左边是可以接收到这个值的,
- 除此之外,在外部手动调用Generator对象的throw方法,这个方法可以对Generator内部抛出异常,通过try…catch捕获这个异常
function ajax (url) {
return new Promise(function (resolve, reject) {
// foo()
// throw new Error()
var xhr = new XMLHttpRequest()
xhr.open('GET', url)
xhr.responseType = 'json'
xhr.onload = function () {
if (this.status === 200) {
resolve(this.response)
} else {
reject(new Error(this.statusText))
}
}
xhr.send()
})
}
function* main () {
const users = yield ajax('/api/users.json')
console.log(users);
}
const g = main()
const result = g.next()
console.log(result);
result.value.then(data => {
g.next(data)
})
function* main () {
const users = yield ajax('/api/users.json')
console.log(users);
const posts = yield ajax('/api/posts.json')
console.log(posts);
}
const g = main()
const result = g.next()
console.log(result);
result.value.then(data => {
const result2 = g.next(data)
result2.value.then(data => {
g.next(data)
})
})
function* main () {
const users = yield ajax('/api/users.json')
console.log(users);
const posts = yield ajax('/api/posts.json')
console.log(posts);
}
const g = main()
const result = g.next()
result.value.then(data => {
const result2 = g.next(data)
if (result2.done) return
result2.value.then(data => {
const result3 = g.next(data)
if (result3.done) return
result3.value.then(data => {
g.next(data)
})
})
})
这里可以使用递归判断是否执行结束
function* main () {
try {
const users = yield ajax('/api/users.json')
console.log(users);
const posts = yield ajax('/api/posts.json')
console.log(posts);
const url = yield ajax('/api/url1.json')
console.log(url);
} catch (e) {
console.log(e);
}
}
const g = main()
// 递归执行
function handleResult (result) {
if (result.done) return // 生成器函数结束
result.value.then(data => {
handleResult(g.next(data))
}, error => {
g.throw(error)
})
}
handleResult(g.next())
可以封装使用
function co (generator) {
const g = generator()
function handleResult (result) {
if (result.done) return // 生成器函数结束
result.value.then(data => {
handleResult(g.next(data))
}, error => {
g.throw(error)
})
}
handleResult(g.next())
}
co(main)
Async / Await 语法糖
语言层面的异步编程标准
async function main () {
try {
const users = await ajax('/api/users.json')
console.log(users);
const posts = await ajax('/api/posts.json')
console.log(posts);
const url = await ajax('/api/url1.json')
console.log(url);
} catch (e) {
console.log(e);
}
}
const promise = main()
promise.then(() => {
console.log('all completed');
})
co
,比较麻烦,在ECMAScript2017中新增了一个Async的函数,提供了扁平化的异步编程体验,是语言层面标准的异步编程语法,使用起来更方便async
, 将内部的 yield
关键词换成了 await
首先分析其原理
- promise就是一个类
在执行类的时候需要传递一个执行器进去,执行器会立即执行- Promise中有三种状态,分别为成功-fulfilled 失败-rejected 等待pending
pending -> fulfilled
pending -> rejected
一旦状态确定就不可更改- resolve和reject函数是用来更改状态的
resolve:fulfilled
reject:rejected- then方法内部做的事情就是判断状态
如果状态是成功,调用成功回调函数
如果状态是失败,调用失败回调函数
then方法是被定义在原型对象中的- then成功回调有一个参数,表示成功之后的值;
then失败回调有一个参数,表示失败后的原因- 同一个promise对象下面的then方法是可以被多次调用的
- then方法是可以被链式调用的,后面then方法的回调函数拿到的值是上一个then方法的回调函数的返回值
// 定义成常量是为了复用且代码有提示,变量没有提示
const PENDING = 'pending' // 等待
const FULFILLED = 'fulfilled' // 成功
const REJECTED = 'rejected' // 失败
// 定义一个构造函数
class MyPromise {
constructor (exector) {
// exector是一个执行器,进入会立即执行,并传入resolve和reject方法
exector(this.resolve, this.reject)
}
// promise状态 实例对象的一个属性,初始为等待
status = PENDING
// 成功之后的值
value = undefined
// 失败之后的原因
reason = undefined
// 定义成箭头函数指向当前类的实例化的对象
resolve = value => {
// 判断状态是不是等待,如果不是等待(pending)则阻止程序向下执行
if (this.status !== PENDING) return
// 将状态更改为成功
this.status = FULFILLED
// 保存成功之后的值
this.value = value
}
reject = reason => {
if (this.status !== PENDING) return
// 将状态更改为失败
this.status = REJECTED
// 保存失败之后的原因
this.reason = reason
}
then (successCallback, failCallback) {
// 先判断状态
if (this.status === FULFILLED) {
// 调用成功回调,并把值返回
successCallback(this.value)
} else if (this.status === REJECTED) {
// 调用失败回调,并把原因返回
failCallback(this.reason)
}
}
}
module.exports = MyPromise
const myPromise = require('./myPromise.js')
let promise = new myPromise((resolve, reject)=> {
resolve('成功')
// reject('失败')
})
promise.then(value => {
console.log('value', value);
}, reason => {
console.log('reason', reason);
})
上面是没有经过异步处理的,如果有异步逻辑加进来,会有一些问题
const PENDING = 'pending'
const FULFILLED = 'fulfilled'
const REJECTED = 'rejected'
class MyPromise {
constructor (exector) {
exector(this.resolve, this.reject)
}
status = PENDING
value = undefined
reason = undefined
// 成功回调
successCallback = undefined
// 失败回调
failCallback = undefined
resolve = value => {
if (this.status !== PENDING) return
this.status = FULFILLED
this.value = value
// 判断成功回调是否存在,如果存在就调用
this.successCallback && this.successCallback(this.value)
}
reject = reason => {
if (this.status !== PENDING) return
this.status = REJECTED
this.reason = reason
// 判断失败回调是否存在,如果存在就调用
this.failCallback && this.failCallback(this.reason)
}
then (successCallback, failCallback) {
if (this.status === FULFILLED) {
successCallback(this.value)
} else if (this.status === REJECTED) {
failCallback(this.reason)
} else {
// 等待
// 因为不知道状态,所以将成功回调和失败回调存储起来
// 等待执行成功或失败函数的时候再传递
this.successCallback = successCallback
this.failCallback = failCallback
}
}
}
module.exports = MyPromise
const myPromise = require('./myPromise.js')
let promise = new myPromise((resolve, reject)=> {
// 主线程代码立即执行,setTimeout是异步代码,then会马上执行
// 这个时候判断promise的状态,状态是pending,但是之前并没有判断这个状态,即没有处理异步情况
setTimeout(() => {
resolve('成功')
}, 2000)
})
promise.then(value => {
console.log('value', value);
}, reason => {
console.log('reason', reason);
})
promise的then方法是可以被多次调用的。
这里如果有三个then的调用,分两种情况:
保存到数组中,最后统一执行
const PENDING = 'pending'
const FULFILLED = 'fulfilled'
const REJECTED = 'rejected'
class MyPromise {
constructor (exector) {
exector(this.resolve, this.reject)
}
status = PENDING
value = undefined
reason = undefined
// 定义一个成功回调参数,初始化一个空数组
successCallback = []
// 义一个失败回调参数,初始化一个空数组
failCallback = []
resolve = value => {
if (this.status !== PENDING) return
this.status = FULFILLED
this.value = value
// 判断成功回调是否存在,如果存在就调用
// 循环回调数组,把数组前面的方法弹出来并且直接调用
// shift方法是在数组中删除值,每执行一个就删除一个,最终变为0
while (this.successCallback.length) this.successCallback.shift()(this.value)
}
reject = reason => {
if (this.status !== PENDING) return
this.status = REJECTED
this.reason = reason
// 判断失败回调是否存在,如果存在就调用
// 循环回调数组,把数组前面的方法弹出来并且直接调用
while (this.failCallback.length) this.failCallback.shift()(this.reason)
}
then (successCallback, failCallback) {
if (this.status === FULFILLED) {
successCallback(this.value)
} else if (this.status === REJECTED) {
failCallback(this.reason)
} else {
// 等待
// 将成功回调和失败回调都保存在数组中
this.successCallback.push(successCallback)
this.failCallback.push(failCallback)
}
}
}
module.exports = MyPromise
const myPromise = require('./myPromise.js')
let promise = new myPromise((resolve, reject)=> {
setTimeout(() => {
resolve('成功')
}, 2000)
})
promise.then(value => {
console.log('value', value);
}, reason => {
console.log('reason', reason);
})
promise.then(value => {
console.log('value', value);
}, reason => {
console.log('reason', reason);
})
promise.then(value => {
console.log('value', value);
}, reason => {
console.log('reason', reason);
})
then方法要链式调用那么就需要返回一个promise对象,
then方法的return返回值作为下一个then方法的参数
then方法还能return一个promise对象,那么如果是一个promise对象,那么就需要判断它的状态
const PENDING = 'pending'
const FULFILLED = 'fulfilled'
const REJECTED = 'rejected'
class MyPromise {
constructor (exector) {
exector(this.resolve, this.reject)
}
status = PENDING
value = undefined
reason = undefined
successCallback = []
failCallback = []
resolve = value => {
if (this.status !== PENDING) return
this.status = FULFILLED
this.value = value
while (this.successCallback.length) this.successCallback.shift()(this.value)
}
reject = reason => {
if (this.status !== PENDING) return
this.status = REJECTED
this.reason = reason
while (this.failCallback.length) this.failCallback.shift()(this.reason)
}
then (successCallback, failCallback) {
// this.then方法返回第一个promise对象
let promise2 = new MyPromise((resolve, reject) => {
if (this.status === FULFILLED) {
// x 是上一个 promise 回调函数的 return 返回值
// x 的值是普通值,直接调用 resolve
let x = successCallback(this.value)
resolve(x)
} else if (this.status === REJECTED) {
failCallback(this.reason)
} else {
this.successCallback.push(successCallback)
this.failCallback.push(failCallback)
}
});
return promise2
}
}
module.exports = MyPromise
const myPromise = require('./myPromise.js')
let promise = new myPromise((resolve, reject)=> {
// 目前这里只处理同步的问题
resolve('成功')
})
promise.then(value => {
console.log('value1', value);
return 100
}).then(value => {
console.log('value2', value);
})
const PENDING = 'pending'
const FULFILLED = 'fulfilled'
const REJECTED = 'rejected'
class MyPromise {
constructor (exector) {
exector(this.resolve, this.reject)
}
status = PENDING
value = undefined
reason = undefined
successCallback = []
failCallback = []
resolve = value => {
if (this.status !== PENDING) return
this.status = FULFILLED
this.value = value
while (this.successCallback.length) this.successCallback.shift()(this.value)
}
reject = reason => {
if (this.status !== PENDING) return
this.status = REJECTED
this.reason = reason
while (this.failCallback.length) this.failCallback.shift()(this.reason)
}
then (successCallback, failCallback) {
// this.then方法返回第一个promise对象
let promise2 = new MyPromise((resolve, reject) => {
if (this.status === FULFILLED) {
// x 是上一个 promise 回调函数的 return 返回值
// 判断 x 的值是普通值还是 promise 对象
// 如果是普通值,直接调用 resolve
// 如果是 promise 对象,查看 promise 对象返回的结果
// 再根据 promise 对象返回的结果,决定调用 resolve 还是 reject
let x = successCallback(this.value)
resolvePromise(x, resolve, reject)
} else if (this.status === REJECTED) {
failCallback(this.reason)
} else {
// 等待
// 将成功回调和失败回调都保存在数组中
this.successCallback.push(successCallback)
this.failCallback.push(failCallback)
}
});
return promise2
}
}
function resolvePromise(x, resolve, reject) {
// 判断 x 是不是其实例对象
if (x instanceof MyPromise) {
// promise 对象
// x.then(value => resolve(value), reason => reject(reason))
x.then(resolve, reject)
} else {
// 普通值
resolve(x)
}
}
module.exports = MyPromise
const MyPromise = require('./myPromise.js')
const myPromise = require('./myPromise.js')
let promise = new myPromise((resolve, reject)=> {
resolve('成功')
})
function other () {
return new MyPromise((resolve, reject) => {
resolve('other')
})
}
promise.then(value => {
console.log(1);
console.log('value1', value);
// return 100
return other()
}).then(value => {
console.log(2);
console.log('value2', value);
})
如果then方法返回的是自己的promise对象,则会发生promise的嵌套,这个时候程序会报错
var promise = new Promise(function (resolve, reject) {
resolve(100)
})
var p1 = promise.then(function(value) {
console.log(value);
return p1
})
// => 100
// => Uncaught (in promise) TypeError: Chaining cycle detected for promise #
var promise = new Promise(function (resolve, reject) {
resolve(100)
})
var p1 = promise.then(function(value) {
console.log(value);
return p1
})
p1.then(function () {
}, function (reason) {
console.log(reason.message);
})
// => 100
// => Chaining cycle detected for promise #
所以为了避免这种情况,我们需要改造一下then方法
const PENDING = 'pending'
const FULFILLED = 'fulfilled'
const REJECTED = 'rejected'
class MyPromise {
constructor (exector) {
exector(this.resolve, this.reject)
}
status = PENDING
value = undefined
reason = undefined
successCallback = []
failCallback = []
resolve = value => {
if (this.status !== PENDING) return
this.status = FULFILLED
this.value = value
while (this.successCallback.length) this.successCallback.shift()(this.value)
}
reject = reason => {
if (this.status !== PENDING) return
this.status = REJECTED
this.reason = reason
while (this.failCallback.length) this.failCallback.shift()(this.reason)
}
then (successCallback, failCallback) {
let promise2 = new MyPromise((resolve, reject) => {
if (this.status === FULFILLED) {
// 因为 new Promise 需要执行完成之后才有 promise2 ,同步代码中没有 promise2 ,
// 所以这部分代码需要异步执行
// 使用setTimeout不是为了延迟,而是为了变成异步代码,所以时间写为0
setTimeout(() => {
let x = successCallback(this.value)
// 需要判断then之后的如return的peomise对象和原来的是不是一样的,
// 判断 x 和 promise2 是否相等,所以 resolvePromise 给中传递 promise2 过去
resolvePromise(promise2, x, resolve, reject)
}, 0)
} else if (this.status === REJECTED) {
failCallback(this.reason)
} else {
this.successCallback.push(successCallback)
this.failCallback.push(failCallback)
}
});
return promise2
}
}
function resolvePromise(promise2, x, resolve, reject) {
// 如果相等,说明return的是自己,抛出类型错误并返回
if (promise2 === x) {
return reject(new TypeError('Chaining cycle detected for promise #' ))
}
if (x instanceof MyPromise) {
x.then(resolve, reject)
} else {
resolve(x)
}
}
module.exports = MyPromise
const myPromise = require('./myPromise.js')
let promise = new myPromise((resolve, reject)=> {
resolve(100)
})
let p1 = promise.then(value => {
console.log(value);
return p1
})
p1.then(function () {
}, function (reason) {
console.log(reason.message);
})
// => 100
// => Chaining cycle detected for promise #
目前我们在Promise类中没有进行任何处理,所以我们需要捕获和处理错误。
捕获执行器中的代码,如果执行器中有代码错误,那么promise的状态要弄成错误状态
constructor (exector) {
// 捕获错误,如果有错误就执行reject
try {
exector(this.resolve, this.reject)
} catch (e) {
this.reject(e)
}
}
const myPromise = require('./myPromise.js')
let promise = new myPromise((resolve, reject)=> {
throw new Error('executor error')
resolve(100)
})
promise.then(value => {
console.log('value', value);
}, reason => {
console.log('reason', reason);
})
捕获执行then时报错的信息
then (successCallback, failCallback) {
let promise2 = new MyPromise((resolve, reject) => {
if (this.status === FULFILLED) {
setTimeout(() => {
// 如果回调中报错的话就执行reject
try {
let x = successCallback(this.value)
resolvePromise(promise2, x, resolve, reject)
} catch (e) {
reject(e)
}
}, 0)
} else if (this.status === REJECTED) {
failCallback(this.reason)
} else {
this.successCallback.push(successCallback)
this.failCallback.push(failCallback)
}
});
return promise2
}
const myPromise = require('./myPromise.js')
let promise = new myPromise((resolve, reject)=> {
resolve(100)
})
promise.then(value => {
console.log('value', value);
throw new Error('then error')
}, reason => {
console.log('reason', reason);
}).then(value => {
console.log('value', value);
}, reason => {
console.log('reason', reason);
})
then (successCallback, failCallback) {
let promise2 = new MyPromise((resolve, reject) => {
if (this.status === FULFILLED) {
setTimeout(() => {
// 如果回调中报错的话就执行reject
try {
let x = successCallback(this.value)
resolvePromise(promise2, x, resolve, reject)
} catch (e) {
reject(e)
}
}, 0)
} else if (this.status === REJECTED) {
// 在状态是reject的时候对返回的promise进行处理
setTimeout(() => {
// 如果回调中报错的话就执行reject
try {
let x = failCallback(this.reason)
resolvePromise(promise2, x, resolve, reject)
} catch (e) {
reject(e)
}
}, 0)
} else {
this.successCallback.push(successCallback)
this.failCallback.push(failCallback)
}
});
return promise2
}
const myPromise = require('./myPromise.js')
let promise = new myPromise((resolve, reject)=> {
// throw new Error('executor error')
// resolve(100)
reject('失败')
})
promise.then(value => {
console.log('value', value);
// throw new Error('then error')
}, reason => {
console.log('reason', reason.message);
return 10000
}).then(value => {
console.log(value);
})
const PENDING = 'pending'
const FULFILLED = 'fulfilled'
const REJECTED = 'rejected'
class MyPromise {
constructor (exector) {
// 捕获错误,如果有错误就执行reject
try {
exector(this.resolve, this.reject)
} catch (e) {
this.reject(e)
}
}
status = PENDING
value = undefined
reason = undefined
successCallback = []
failCallback = []
resolve = value => {
if (this.status !== PENDING) return
this.status = FULFILLED
this.value = value
while (this.successCallback.length) this.successCallback.shift()()
}
reject = reason => {
if (this.status !== PENDING) return
this.status = REJECTED
this.reason = reason
while (this.failCallback.length) this.failCallback.shift()()
}
then (successCallback, failCallback) {
let promise2 = new MyPromise((resolve, reject) => {
if (this.status === FULFILLED) {
setTimeout(() => {
// 如果回调中报错的话就执行reject
try {
let x = successCallback(this.value)
resolvePromise(promise2, x, resolve, reject)
} catch (e) {
reject(e)
}
}, 0)
} else if (this.status === REJECTED) {
// 在状态是reject的时候对返回的promise进行处理
setTimeout(() => {
// 如果回调中报错的话就执行reject
try {
let x = failCallback(this.reason)
resolvePromise(promise2, x, resolve, reject)
} catch (e) {
reject(e)
}
}, 0)
} else {
this.successCallback.push(() => {
setTimeout(() => {
// 如果回调中报错的话就执行reject
try {
let x = successCallback(this.value)
resolvePromise(promise2, x, resolve, reject)
} catch (e) {
reject(e)
}
}, 0)
})
this.failCallback.push(() => {
setTimeout(() => {
// 如果回调中报错的话就执行reject
try {
let x = failCallback(this.reason)
resolvePromise(promise2, x, resolve, reject)
} catch (e) {
reject(e)
}
}, 0)
})
}
});
return promise2
}
}
function resolvePromise(promise2, x, resolve, reject) {
// 如果相等,说明return的是自己,抛出类型错误并返回
if (promise2 === x) {
return reject(new TypeError('Chaining cycle detected for promise #' ))
}
if (x instanceof MyPromise) {
x.then(resolve, reject)
} else {
resolve(x)
}
}
module.exports = MyPromise
const myPromise = require('./myPromise.js')
let promise = new myPromise((resolve, reject)=> {
setTimeout(() => {
resolve('成功......')
}, 2000)
})
promise.then(value => {
console.log('value1', value);
return '123'
}, reason => {
console.log('reason1', reason.message);
return 10000
}).then(value => {
console.log('value2', value);
}, reason => {
console.log('reason2', reason.message);
})
// => value1 成功......
// => value2 123
then方法的两个参数都是可选参数,我们可以不传参数。
下面的参数可以传递到最后进行返回
var promise = new Promise(function (resolve, reject) {
resolve(100)
})
promise
.then()
.then()
.then(value => console.log(value))
// => 100
// 相当于
promise
.then(value => value)
.then(value => value)
.then(value => console.log(value))
所以我们修改一下then方法
then (successCallback, failCallback) {
// 这里进行判断,如果有回调就选择回调,如果没有回调就传一个函数,把参数传递下去
successCallback = successCallback ? successCallback : value => value
// 错误函数也是进行赋值,把错误信息抛出
failCallback = failCallback ? failCallback : reason => reason
let promise2 = new MyPromise((resolve, reject) => {
if (this.status === FULFILLED) {
setTimeout(() => {
try {
let x = successCallback(this.value)
resolvePromise(promise2, x, resolve, reject)
} catch (e) {
reject(e)
}
}, 0)
} else if (this.status === REJECTED) {
setTimeout(() => {
try {
let x = failCallback(this.reason)
resolvePromise(promise2, x, resolve, reject)
} catch (e) {
reject(e)
}
}, 0)
} else {
this.successCallback.push(() => {
setTimeout(() => {
try {
let x = successCallback(this.value)
resolvePromise(promise2, x, resolve, reject)
} catch (e) {
reject(e)
}
}, 0)
})
this.failCallback.push(() => {
setTimeout(() => {
try {
let x = failCallback(this.reason)
resolvePromise(promise2, x, resolve, reject)
} catch (e) {
reject(e)
}
}, 0)
})
}
});
return promise2
}
resolve之后
onst myPromise = require('./myPromise.js')
let promise = new myPromise((resolve, reject)=> {
resolve('成功')
})
promise
.then()
.then()
.then(value => console.log(value))
// => 成功
reject之后
const myPromise = require('./myPromise.js')
let promise = new myPromise((resolve, reject)=> {
reject('失败')
})
promise
.then()
.then()
.then(value => console.log(value), reason => console.log(reason))
// => 失败
promise.all方法是解决异步并发问题的
// 如果p1是两秒之后执行的,p2是立即执行的,那么根据正常的是p2在p1的前面
// 如果我们在all中指定了执行顺序,那么会根据我们传递的顺序进行执行
function p1() {
return new Promise((resolve, reject) => {
setTimeout(function() {
resolve('p1')
}, 2000)
})
}
function p2() {
return new Promise((resolve, reject) => {
resolve('p2')
})
}
Promise.all(['a', 'b', p1(), p2(), 'c']).then(function(result) {
console.log(result);
// => ["a", "b", "p1", "p2", "c"]
})
分析一下:
// promise.all是一个静态方法,所以需要加static声明为静态方法
static all (array) {
// 结果数组
let result = []
// 计数器
let index = 0
return new MyPromise((resolve, reject) => {
let addData = (key, value) => {
result[key] = value
index ++
// 如果计数器和数组的长度相同,说明所有的元素都已执行完毕,可以输出了
if (index === array.length) {
resolve(result)
}
}
for (let i = 0; i< array.length; i++) {
let currrent = array[i]
if (currrent instanceof MyPromise) {
// promise对象就执行then,如果是resolve就把值添加到数组中去,如果是错误就执行reject返回
currrent.then(value => addData(i, value), reason => reject(reason))
} else {
// 普通值就添加到对应的数组中去
addData(i, array[i])
}
}
})
}
const MyPromise = require('./myPromise.js')
function p1() {
return new MyPromise((resolve, reject) => {
setTimeout(function() {
resolve('p1')
}, 2000)
})
}
function p2() {
return new MyPromise((resolve, reject) => {
resolve('p2')
})
}
MyPromise.all(['a', 'b', p1(), p2(), 'c']).then(function(result) {
console.log(result);
// => ["a", "b", "p1", "p2", "c"]
})
function p1() {
return new Promise((resolve, reject) => {
setTimeout(function() {
resolve('p1')
}, 2000)
})
}
function p2() {
return new Promise((resolve, reject) => {
resolve('p2')
})
}
Promise.resolve(100).then(value => console.log(value))
// => 100
Promise.resolve(p1()).then(value => console.log(value))
// => p1
static resolve (value) {
// 如果是promise对象,就直接返回
if (value instanceof MyPromise) return value
// 如果是普通值,就返回一个promise对象
return new Promise(resolve => resolve(value))
}
const MyPromise = require('./myPromise.js')
function p1() {
return new MyPromise((resolve, reject) => {
setTimeout(function() {
resolve('p1')
}, 2000)
})
}
function p2() {
return new MyPromise((resolve, reject) => {
resolve('p2')
})
}
MyPromise.resolve(100).then(value => console.log(value))
// => 100
MyPromise.resolve(p1()).then(value => console.log(value))
// => p1
function p1() {
return new Promise((resolve, reject) => {
resolve('hello')
})
}
p1().finally(() => {
console.log('finally');
}).then(value => console.log(value))
// => finally
// => hello
finally (callback) {
// 拿到当前的promise的状态,可以使用then方法,而且不管怎样都返回callback
// 而且then方法就是返回一个promise对象,那么我们直接返回then方法调用之后的结果即可
// 我们需要在回调之后拿到成功的回调,所以需要把value也return出去
// 失败的回调也要抛出原因
// 如果callback是一个异步的promise对象,还需要等待其执行完毕,所以需要静态方法resolve
return this.then(value => {
// bacallback调用之后返回的promise传递出去,并且执行promise,且在成功之后返回value
return MyPromise.resolve(callback()).then(() => value)
}, reason => {
// 失败之后调用then方法,然后把失败的原因返回出去
return MyPromise.resolve(callback()).then(() => {
throw reason})
})
}
const MyPromise = require('./myPromise.js')
function p1() {
return new MyPromise((resolve, reject) => {
// reject('hello')
resolve('hello')
})
}
function p2() {
return new MyPromise((resolve, reject) => {
setTimeout(function() {
resolve('p2')
}, 2000)
})
}
p1().finally(() => {
console.log('finally');
return p2()
}).then(value => console.log(value), reason => console.log(reason))
// => finally
// => hello
catch (failCallback) {
return this.then(undefined, failCallback)
}
const MyPromise = require('./myPromise.js')
function p1() {
return new MyPromise((resolve, reject) => {
reject('reject')
// resolve('hello')
})
}
p1()
.then(value => console.log(value))
.catch(reason => console.log(reason))
// => reject