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 run
f1 is run
result f1 done
result 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');
})
// 输入
aaaa
Promise {<pending>}
bbb
将永远不会被打印进去
对于第 9 点,举个例子:
setTimeout(() => {console.log('three');
},0)
Promise.resolve().then(() => {console.log('two');
})
console.log('one');
// 输入
one
two
three
one
是立刻输入two
是在“本轮”事件循环的开端执行而后输入 three
是下一轮事件循环开始时被执行而后输入
写的有点抽象 后续会再补充 有问题欢送一起探讨