【拉钩教育】大前端技术分享
Part1 · JavaScript【深度剖析】
函数式编程与Js异步编程、手写Promise
文章说明:本专栏内容为本人参加【拉钩大前端高新训练营】的学习笔记以及思考总结,学徒之心,仅为分享。如若有误,请在评论区支出,如果您觉得专栏内容还不错,请点赞、关注、评论。
共同进步!
前情提要:
上一篇中提到了【函子】,点击上方链接可查看。
本篇主要从代码角度去了解primise,基本用法都写在注释内,若有疑问,欢迎留言
九、JavaScript异步编程
1.同步模式
同步模式指的就是我们代码中的任务依次执行,程序执行的顺序与代码的编写顺序一致。
以下代码为同步模式的代码,具体分析其执行顺序
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在读取到代码时,先将一个(anonymous)匿名函数放到调用栈。
在读取到第一行console.log(‘Global begin’)时,将其压到调用栈,随后去执行,当控制台打印出结果后,将其弹出调用栈,继续下一行代码;
在读取到bar函数及foo函数时,由于其并未执行,因此调用栈内无执行任务;
在读取到foo()函数调用时,首先将foo()压入调用栈,遇console.log(‘Foo task’)代码,将其压入调用栈,执行完毕后弹出调用栈。随后将bar()压入调用栈,程序去bar()函数内部解析,将console.log(‘Bar task’)压入调用栈。foo()函数执行完毕后,依次将bar()、foo()函数弹出调用栈;
最后将console.log(‘Global end’)压入调用栈并执行,随后将其弹出,代码运行完成,将(anonymous)弹出调用栈,程序完全结束。
2.异步模式
在浏览器端,耗时很长的操作都应该异步执行,避免浏览器失去响应,最好的例子就是Ajax操作。在服务器端,"异步模式"甚至是唯一的模式,因为执行环境是单线程的,如果允许同步执行所有http请求,服务器性能会急剧下降,很快就会失去响应。
异步调用并不会阻止代码的顺序执行,而是在将来的某一个时刻触发设置好的逻辑,所以我们
- 并不知道逻辑什么时候会被调用
- 只能定义当触发的时候逻辑是什么
- 只能等待,同时可以去处理其他的逻辑
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
分析:
首先分析代码结构,本段代码为异步模式,js在读取到代码时,先将一个(anonymous)匿名函数放到调用栈。
将第一行console.log(‘global begin’)压入调用栈并执行后弹出,此时控制台打印global begin;
程序到setTimeout时,首先将setTimeout(timer1)压入调用栈,在web API线程放入timer1计时器,倒计时1.8s,随后将setTimeout(timer1)弹出调用栈;
同上步,将setTimeout(timer2)压入调用栈,web API线程放入timer2计时器,倒计时1s,随后将setTimeout(timer2)弹出调用栈;
随后将console.log(‘global end’)压入调用栈并执行后弹出调用栈,代码执行完毕,将anonymous弹出调用栈;
web API将timer1与timer2依次放入事件队列,此时timer2优先倒计时完毕,进入调用栈,然后执行内部代码。将console.log(‘timer2 invoke’)压入调用栈并执行后弹出。
随后遇setTimeout(inner),将其压入调用栈并在Web API加入inner计时器,倒计时1s。setTimeout(inner)弹出调用栈。
此时随倒计时,timer1倒计时完毕,程序进入timer1内部,将console.log(‘timer1 invoke’)压入调用栈并执行后弹出。
随后inner()计时器进入任务队列,在倒计时结束后,压入调用栈并执行后弹出。至此,程序执行完毕。
---------插入图片
3.回调函数
回调函数指的是需要在将来不确定的某一时刻异步调用的函数。通常,在这种回调函数中,我们经常需要频繁地访问外部数据。
function foo(callback) {
setTimeout(function () {
callback()
}, 3000)
}
foo(function () {
console.log('这就是一个回到函数')
console.log('调用者定义这个函数,执行者执行这个函数')
console.log('起始就是调用者告诉执行者:异步任务结束后应该做什么')
})
4.Promise概述
由于上述回调函数可以存在嵌套关系,因此容易导致回调地狱问题,即产生如下代码:
$.get('url1', function (data1) {
$.get('url2', function (data2) {
$.get('url3', function (data3) {
$.get('url4', function (data4) {
$.get('url5', function (data5) {
$.get('url6', function (data6) {
$.get('url7', function (data7) {
// 略微夸张,但实际存在
})
})
})
})
})
})
})
因此提出Promise承诺,即在回调函数中,承诺在异步完成后下一步干什么。
Promise存在三种状态,及承诺开始pending,承诺兑现fulfilled以及承诺失败rejected。并且,承诺的状态一旦确定就不可在被改变,即当状态为fulfilled时,此promise的状态就不可再变为rejected,反之同样。
fulfilled与rejected存在onFulfilled和onRejected状态。
5.Promise基本用法
// Promise 基本示例
const promise = new Promise(function (resolve, reject) {
// 这里用于“兑现”承诺
// resolve(100) // 承诺达成
reject(new Error('promise rejected')) // 承诺失败
})
promise.then(function (value) {
// 即便没有异步操作,then 方法中传入的回调仍然会被放入队列,等待下一轮执行
console.log('resolved', value)
}, function (error) {
console.log('rejected', error)
})
console.log('end')
6.Promise使用案例
// Promise 方式的 AJAX
function ajax (url) {
// 返回一个promise对象
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/foo.json').then(function (res) {
console.log(res) // ajax请求成功后调用
}, function (error) {
console.log(error) // ajax请求失败后调用
})
7.Promise常见误区
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(urls.users).then(function (users) {
ajax(urls.users).then(function (users) {
ajax(urls.users).then(function (users) {
ajax(urls.users).then(function (users) {
})
})
})
})
})
嵌套使用就失去了Promise通过状态变化获取结果的优势,依然无法获取其中某个回调是否完成的结果。
8.Promise链式调用
// 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()
})
}
// var promise = ajax('/api/users.json')
// var promise2 = promise.then(
// function onFulfilled (value) {
// console.log('onFulfilled', value)
// },
// function onRejected (error) {
// console.log('onRejected', error)
// }
// )
// console.log(promise2 === promise)
ajax('/api/users.json')
.then(function (value) {
console.log(1111)
return ajax('/api/urls.json')
}) // => Promise
.then(function (value) {
console.log(2222)
console.log(value)
return ajax('/api/urls.json')
}) // => Promise
.then(function (value) {
console.log(3333)
return ajax('/api/urls.json')
}) // => Promise
.then(function (value) {
console.log(4444)
return 'foo'
}) // => Promise
.then(function (value) {
console.log(5555)
console.log(value)
})
9.Promise异常处理
// 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()
})
}
// ajax('/api/users11.json')
// .then(function onFulfilled (value) {
// console.log('onFulfilled', value)
// }, function onRejected (error) {
// console.log('onRejected', error)
// })
// 使用 catch 注册失败回调是更常见的
// ajax('/api/users11.json')
// .then(function onFulfilled (value) {
// console.log('onFulfilled', value)
// })
// .catch(function onRejected (error) {
// console.log('onRejected', error)
// })
// then(onRejected) 实际上就相当于 then(undefined, onRejected)
// ajax('/api/users11.json')
// .then(function onFulfilled (value) {
// console.log('onFulfilled', value)
// })
// .then(undefined, function onRejected (error) {
// console.log('onRejected', error)
// })
// 同时注册的 onRejected 只是给当前 Promise 对象注册的失败回调
// 它只能捕获到当前 Promise 对象的异常
// ajax('/api/users.json')
// .then(function onFulfilled (value) {
// console.log('onFulfilled', value)
// return ajax('/error-url')
// }, function onRejected (error) {
// console.log('onRejected', error)
// })
// 因为 Promise 链条上的任何一个异常都会被一直向后传递,直至被捕获
// 分开注册的 onRejected 相当于给整个 Promise 链条注册失败回调
ajax('/api/users.json')
.then(function onFulfilled (value) {
console.log('onFulfilled', value)
return ajax('/error-url')
}) // => Promise {}
// .catch(function onRejected (error) {
// console.log('onRejected', error)
// })
// 全局捕获 Promise 异常,类似于 window.onerror
window.addEventListener('unhandledrejection', event => {
const { reason, promise } = event
console.log(reason, promise)
// reason => Promise 失败原因,一般是一个错误对象
// promise => 出现异常的 Promise 对象
event.preventDefault()
}, false)
// Node.js 中使用以下方式
// process.on('unhandledRejection', (reason, promise) => {
// console.log(reason, promise)
// // reason => Promise 失败原因,一般是一个错误对象
// // promise => 出现异常的 Promise 对象
// })
10.Promise静态方法
// 常用 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()
})
}
// Promise.resolve('foo')
// .then(function (value) {
// console.log(value)
// })
// new Promise(function (resolve, reject) {
// resolve('foo')
// })
// 如果传入的是一个 Promise 对象,Promise.resolve 方法原样返回
// var promise = ajax('/api/users.json')
// var promise2 = Promise.resolve(promise)
// console.log(promise === promise2)
// 如果传入的是带有一个跟 Promise 一样的 then 方法的对象,
// Promise.resolve 会将这个对象作为 Promise 执行
// Promise.resolve({
// then: function (onFulfilled, onRejected) {
// onFulfilled('foo')
// }
// })
// .then(function (value) {
// console.log(value)
// })
// Promise.reject 传入任何值,都会作为这个 Promise 失败的理由
// Promise.reject(new Error('rejected'))
// .catch(function (error) {
// console.log(error)
// })
Promise.reject('anything')
.catch(function (error) {
console.log(error)
})
11.Promise并行执行
// 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()
})
}
// ajax('/api/users.json')
// ajax('/api/posts.json')
// var promise = Promise.all([
// ajax('/api/users.json'),
// ajax('/api/posts.json')
// ])
// promise.then(function (values) {
// console.log(values)
// }).catch(function (error) {
// console.log(error)
// })
// 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(value => {
console.log(value)
})
.catch(error => {
console.log(error)
})
12.Promise执行时序
// 微任务
console.log('global start')
// setTimeout 的回调是 宏任务,进入回调队列排队
setTimeout(() => {
console.log('setTimeout')
}, 0)
// Promise 的回调是 微任务,本轮调用末尾直接执行
Promise.resolve()
.then(() => {
console.log('promise')
})
.then(() => {
console.log('promise 2')
})
.then(() => {
console.log('promise 3')
})
console.log('global end')
// Promise vs. Callback
function ajax (url, callback) {
const executor = (resolve, reject) => {
var xhr = new XMLHttpRequest()
xhr.open('GET', url)
xhr.responseType = 'json'
xhr.onload = () => {
if (xhr.status === 200) {
resolve(xhr.response)
} else {
reject(new Error(xhr.statusText))
}
}
xhr.send()
}
if (typeof callback === 'function') {
// support callback
executor(
res => callback(null, res),
err => callback(error)
)
}
return new Promise(executor)
}
// ajax('/api/urls.json', (error, value) => {
// if (error) {
// return console.error(error)
// }
// console.log(value)
// })
// ajax('/api/urls.json')
// .then(value => {
// console.log(value)
// })
// .catch(error => {
// console.error(error)
// })
// 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/url1')
.then(value => {
return ajax('ajax/url2')
})
.then(value => {
return ajax('ajax/url3')
})
.then(value => {
return ajax('ajax/url4')
})
.catch(error => {
console.error(error)
})
// 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.error(e)
}
13.Generator异步方案
// 生成器函数回顾
function * foo () {
console.log('start')
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 配合 Promise 的异步方案
function ajax (url) {
return new Promise((resolve, reject) => {
var xhr = new XMLHttpRequest()
xhr.open('GET', url)
xhr.responseType = 'json'
xhr.onload = () => {
if (xhr.status === 200) {
resolve(xhr.response)
} else {
reject(new Error(xhr.statusText))
}
}
xhr.send()
})
}
function * main () {
try {
const users = yield ajax('/api/users.json')
console.log(users)
const posts = yield ajax('/api/posts.json')
console.log(posts)
const urls = yield ajax('/api/urls11.json')
console.log(urls)
} catch (e) {
console.log(e)
}
}
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)
// 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)
// })
// })
// })
14.Async方案
// Async / Await 语法糖
function ajax (url) {
return new Promise((resolve, reject) => {
var xhr = new XMLHttpRequest()
xhr.open('GET', url)
xhr.responseType = 'json'
xhr.onload = () => {
if (xhr.status === 200) {
resolve(xhr.response)
} else {
reject(new Error(xhr.statusText))
}
}
xhr.send()
})
}
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())
}
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 urls = await ajax('/api/urls.json')
console.log(urls)
} catch (e) {
console.log(e)
}
}
// co(main)
const promise = main()
promise.then(() => {
console.log('all completed')
})
本篇主要从代码角度去了解primise,基本用法都写在注释内,若有疑问,欢迎留言
本文地址:https://blog.csdn.net/weixin_42122355/article/details/109379872