JS为何有异步?
javascript
是单线程的语言,即一次只能实现一个工作,若有多个工作要执行,则必须依照队列排队实现工作,前一个工作执行完能力执行下一个工作。
那么如果上一个工作不完结,下一个工作就永远得不到执行,或者上一个工作执行很久,前后两个工作没有什么必然的分割,白白浪费了工夫在期待。
所以须要异步。
开发中罕用的异步操作
- 网络申请
IO
操作readfile readDir
- 定时函数
setTimeout setInterval
等 - 在Node.js中 还有
process.nextTick() setImmediate()
传统的异步解决方案:
- 事件的订阅/公布机制
公布/订阅机制
简略示例
// 订阅emitter.on('eventName', function(message){ console.log(message)})// 公布emitter.emit('eventName', 'I am a message')
在Node.js
中 咱们能够看到它的利用
var options = { hostname: '127.0.0.1', port: 10086, path: '/pay/pay_callback?' + content, method: 'GET' }; var req = http.request(options, function (res) { console.log('STATUS: ' + res.statusCode); console.log('HEADERS: ' + JSON.stringify(res.headers)); res.setEncoding('utf8'); res.on('data', function (chunk) { console.log('BODY: ' + chunk); }); res.on('end', function() { })}); req.on('error', function (e) { console.log('problem with request: ' + e.message); }); req.end();
下面是一个申请接口的过程, 作为开发者,咱们只须要关注error、data、end
这些业务事件点上即可,订阅了这些事件,它在执行外部流程会主动去触发相应的事件
传统的回调函数的形式会造成回调天堂,相似这种
fs.readFile('some1.json', (err, data) => { fs.readFile('some2.json', (err, data) => { fs.readFile('some3.json', (err, data) => { fs.readFile('some4.json', (err, data) => { }) }) })})
Promise躲避了这一点,应用链式调用的模式,可读性更高
readFilePromise('some1.json').then(data => { return readFilePromise('some2.json')}).then(data => { return readFilePromise('some3.json')}).then(data => { return readFilePromise('some4.json')})
异步变同步
这是咱们开发时常常要遇到的场景 异步代码同步执行。
写两个模仿的异步函数
var f1 = function() { return new Promise(resolve => { setTimeout(() => { console.log('f1 is run'); resolve('f1 done') }, 3000) })}var f2 = function() { return new Promise(resolve => { setTimeout(() => { console.log('f2 is run'); resolve('f2 done') }, 2000) })}
Promise
f1().then(res => { return f2()}).then(res => {})// 输入3s后输入:f1 is run再过2s后输入:f2 is run
如果异步函数很多的话,会有很多个
then
f1().then(res => { return f2()}).then(res => { return f3()}).then...
能够用更简洁的写法
var arr = [f1, f2] // 待执行promise数组var p = Promise.resolve()for(let pro of arr) { p = p.then(res => pro(res))}
reduce
其实跟1是一样的var arr = [f1, f2]arr.reduce((p,c) => { return p.then((res) => c(res))}, Promise.resolve())
async await
var arr = [f1, f2]async function doFunc() { for(let p of arr) { await p() }}
Generator
function * gen() { yield f1() yield f2()}let g = gen()g.next()g.next()
以上的形式都能达到继发执行的后果.
1.2.3.4都是继发执行 而后输入。如果是并发执行 而后按程序输入呢? 有点相似Promise.all
,其实就是并发执行,而后将执行后果先存起来,再程序输入.这样对于两个无关联的异步函数 并发执行的效率更高
async function testFunc() { var arr = [f1, f2] // 并发执行 var promiseArr = arr.map((fn) => fn()) for(let result of promiseArr) { // 同步返回后果 console.log('result', await result); }}// 输入f2 is runf1 is runresult f1 doneresult f2 done
Promise
对于Promise,咱们要按要点来记忆它
- 很长的链式调用
Promise
没有被resolve
或reject
, 它将始终处于pending
的状态
- 如果是在
pending
的状态 无奈晓得是刚开始还是快要完结
- 如果是在
- 无奈勾销
Promise
一旦建设它就会立刻执行,无奈中途勾销
- 无奈勾销
- 如果不设置回调函数,
Promise
外部抛出的谬误,不会反馈到内部(所以个别倡议promise
对象前面要跟着catch
办法)
- 如果不设置回调函数,
- 一旦状态扭转,就永恒放弃该状态,不会再变了
- 如果是链式调用 原
promise
对象的状态跟新对象保持一致
- 如果是链式调用 原
reject
的作用 等同于抛出谬误
catch
办法返回的也是promise
对象(前面能够持续跟then
)
- 立刻
resolve()
的Promise
对象,是在本轮“事件循环”(event loop)
的完结时执行,而不是在下一轮“事件循环”的开始时
- 立刻
对于第2点,举个例子:
function pend () { return new Promise(resolve => { // 没有resolve console.log('aaaa'); })}pend().then(() => { console.log('bbb');})// 输入aaaaPromise {<pending>}
bbb
将永远不会被打印进去
对于第9点,举个例子:
setTimeout(() => { console.log('three');},0)Promise.resolve().then(() => { console.log('two');})console.log('one');// 输入onetwothree
one
是立刻输入two
是在“本轮”事件循环的开端执行而后输入three
是下一轮事件循环开始时被执行而后输入
写的有点抽象 后续会再补充 有问题欢送一起探讨