前言:大家好,我叫邵威儒,大家都喜歡喊我小邵,學的金融專業卻憑藉興趣愛好入了程式猿的坑,從大學買的第一本vb和自學vb,我就與程式設計結下不解之緣,隨後自學易語言寫遊戲輔助、交易軟體,至今進入了前端領域,看到不少朋友都寫文章分享,自己也弄一個玩玩,以下文章純屬個人理解,便於記錄學習,肯定有理解錯誤或理解不到位的地方,意在站在前輩的肩膀,分享個人對技術的通俗理解,共同成長!
後續我會陸陸續續更新javascript方面,儘量把javascript這個學習路徑體系都寫一下
包括前端所常用的es6、angular、react、vue、nodejs、koa、express、公眾號等等
都會從淺到深,從入門開始逐步寫,希望能讓大家有所收穫,也希望大家關注我~
原始碼地址:github.com/iamswr/prom…
在看這篇文章之前可以先看看整個非同步的發展 juejin.im/post/5b6e5c…
Author: 邵威儒
Email: 166661688@qq.com
Wechat: 166661688
github: github.com/iamswr/
主要講一下Generator和co庫、async await,配合使用
Generator
Generator 基本使用
Generator是一個生成器,生成出一個迭代器,主要是用來控制非同步流程的,目前在現有的庫中還是比較少看到generator的,目前主要使用generator的是redux-saga這個庫,koa1.0也是用generator,但是現在都改為async/await。
generator生成器看起來很像普通函式,但是它是在函式名前加了一個 * 號
function* say(){ // 在函式名前加 *
}
複製程式碼
生成器函式可以暫停,而普通函式則是預設一路到底執行程式碼,生成器函式在內部碰到yield就可以實現暫停功能,使用next進行迭代
function* say(){
yield 1
yield 2
yield 3
return 'end'
}
// 1.執行這個生成器看起來跟執行普通函式差不多,
// 但是實際上,執行這個生成器,會返回一個迭代器
let it = say()
// 2.此時it是一個迭代器 iterator,列印輸出是 {}
console.log(it)
let obj1 = it.next()
// 3. 使用next進行迭代,列印輸出 { value: 1, done: false }
// 可以看出,我們執行say生成器,用it來接收生成器返回的迭代器,
// 而通過迭代器it執行next(),會返回 { value: 1, done: false }
// 這裡的value代表的是上面yield後的值,依次返回,
// done為false,意思是還沒結束,後面還有yield或者return,當走到return時
// done會變為true,也就是完成了
console.log(obj1)
複製程式碼
我們這樣完整看一下
function* say() {
let a = yield 1 // 第一個it.next()時返回
let b = yield 2 // 第二個it.next()時返回
let c = yield 3 // 第三個it.next()時返回
return 'end' // 第四個it.next()時返回
}
let it = say()
let obj1 = it.next()
console.log(obj1) // { value: 1, done: false }
let obj2 = it.next()
console.log(obj2) // { value: 2, done: false }
let obj3 = it.next()
console.log(obj3) // { value: 3, done: false }
let obj4 = it.next()
console.log(obj4) // { value: 'end', done: true }
複製程式碼
迭代器,要我們自行一個一個去迭代,一般我們會通過下面這樣進行迭代
function* say() {
let a = yield 1 // 第一個it.next()時返回
let b = yield 2 // 第二個it.next()時返回
let c = yield 3 // 第三個it.next()時返回
return 'end' // 第四個it.next()時返回
}
let it = say()
function next(){
let { value,done } = it.next()
console.log(value) // 依次列印輸出 1 2 3 end
if(!done) next() // 直到迭代完成
}
next()
複製程式碼
通過上面的例子我們大概明白generator大概是怎麼執行的了,
那麼下面我們講講,怎麼往generator裡面放東西。
function* say() {
let a = yield 'hello swr1'
console.log(a)
let b = yield 'hello swr2'
console.log(b)
}
let it = say() // 返回迭代器
// 列印輸出 { value: 'hello swr1', done: false }
// 此時執行迭代器的第一個next,會把上圖紅色圈的區域執行,並且輸出'hello swr1'
// 此時需要注意的是let a = yield 'hello swr1',並非是把yield 'hello swr1'
// 賦值給a,那麼a是什麼時候被賦值呢?我們接著看下面
console.log(it.next())
// 列印輸出 我是被傳進來的1
// { value: 'hello swr2', done: false }
// 此時我們在next裡傳參,實際上就是當執行第二個next的時候,
// 會把上面藍色圈的區域執行,而這個next的引數,
// 會被賦值給a,然後執行console.log(a),然後把'hello swr2'輸出
console.log(it.next('我是被傳進來的1'))
// 列印輸出 我是被傳進來的2
// { value: undefined, done: true }
// 此時我們第三次執行next,實際上就是當執行了第三個next的時候,
// 會把上面黃色圈的區域執行,而這個next的引數,
// 會被賦值給b,然後執行console.log(b),然後因為沒有顯式寫return xxx,
// 會被預設返回undefined
console.log(it.next('我是被傳進來的2'))
複製程式碼
寫到這裡,我和大家一樣不解,這東西到底有什麼用,而且這樣一個一個next迭代,也很繁瑣,下面就來點實用的,generator可以和promise配合使用。
generator和Promise、co配合使用
在講generator和Promise、co配合使用之前我會講一下promise化的函式怎麼寫,因為我們日常開發當中,經常會使用到promise,用一次,就寫一大堆程式碼也不好,那麼我們會考慮到寫一個通用的函式,可以把回撥函式方式的函式,改為promise
// 假設我們有3個檔案,1.txt對應的內容為文字'2.txt'
2.txt對應的內容為文字'3.txt'
3.txt對應的內容為文字'hello swr'
let fs = require('fs')
// promise化函式
function promisify(fn){
return function(...args){
return new Promise((resolve,reject)=>{
fn(...args,(err,data)=>{ // node的api第一個引數為err
if(err) reject(err)
resolve(data)
})
})
}
}
// 把fs.readFile函式promise化
let read = promisify(fs.readFile)
read('1.txt','utf8').then((data)=>{
console.log(data) // 列印輸出為 '2.txt'
})
複製程式碼
這樣我們就完成了一個通用的promise化函式。
接下來我們要去了解一下co庫
co庫地址:github.com/tj/co
$ npm install co
// 本處程式碼promisify源用上面的函式promisify
// 本處程式碼read源用上面的函式read
let co = require('co')
function* r(){
let r1 = yield read('1.txt','utf8')
let r2 = yield read(r1,'utf8')
let r3 = yield read(r2,'utf8')
return r3
}
// 此時我們想取到r3,也就是3.txt裡的內容'hello swr'
// 方法一:
let it = r()
let { value,done } = it.next() // value為一個promise物件
// 該物件會把resolve的值傳給下一個then
value.then((data)=>{ // data值為'2.txt'
let { value,done } = it.next(data)
return value
}).then((data)=>{ // data值為'3.txt'
let { value,done } = it.next(data)
return value
}).then((data)=>{ // data值為'hello swr'
console.log(data) // 列印輸出 'hello swr'
})
// 這樣的寫法,反而顯得很繁瑣複雜了,那麼我們下面看下使用generator+co是怎麼使用的
// 方法二:
co(r()).then(data=>{
console.log(data) // 列印輸出 'hello swr'
})
// 是不是發現generator+co非常高效?
// 程式碼更像同步程式碼了,那麼接下來我們自己實現一個co~
複製程式碼
co是如何實現呢?
function co(it){
return new Promise((resolve,reject)=>{
function next(data){
let { value,done } = it.next(data)
if(!done){
value.then((data)=>{
next(data)
},reject)
}else{
resolve(value)
}
}
next()
})
}
複製程式碼
當有兩個gennerator函式時,並且其中一個巢狀另外一個
function* a(){
yield 1
}
function* b(){
// 1. 當我們想在generator b中巢狀generator a時,怎麼巢狀呢?
// 2. yield *a(); ==> yield 1,實際上就是把yield 1 放在這個位置
// 在生成器函式中使用生成器函式 需要使用 *
yield *a()
yield 2
}
let it = b()
console.log(it.next())
複製程式碼
async await
// 假設我們有3個檔案,1.txt對應的內容為文字'2.txt'
2.txt對應的內容為文字'3.txt'
3.txt對應的內容為文字'hello swr'
async function r(){
try{
let r1 = await read('1.txt','utf8')
let r2 = await read(r1,'utf8')
let r3 = await read(r2,'utf8')
return r3
}catch(e){
console.log(e)
}
}
r().then((data)=>{
console.log(data) // hello swr
})
複製程式碼
有沒發現,async + await = generator + co?
async await解決了非同步問題
- 可以讓程式碼像同步
- 可以使用try catch
- 可以使用promise
- 如果let r1 = await 後面等待的是promise,那麼會把promise的結果賦值給前面的r1,如果let r1 = await 後面等待的是普通值,那麼就會把這個普通值賦值給前面的r1
// 那麼我想把上面的3個請求改為併發的呢?
let arr = Promise.all([read('1.txt','utf8'),read('2.txt','utf8'),read('3.txt','utf8')])
複製程式碼
那麼async await通過babel編譯後是怎樣的呢?
'use strict';
var r = function () {
var _ref = _asyncToGenerator( // async await被編譯成generator /*#__PURE__*/regeneratorRuntime.mark(function _callee() {
var r1, r2, r3;
return regeneratorRuntime.wrap(function _callee$(_context) {
while (1) {
switch (_context.prev = _context.next) {
case 0:
_context.prev = 0;
_context.next = 3;
return read('100.txt', 'utf8');
case 3:
r1 = _context.sent;
_context.next = 6;
return read(r1, 'utf8');
case 6:
r2 = _context.sent;
_context.next = 9;
return read(r2, 'utf8');
case 9:
r3 = _context.sent;
return _context.abrupt('return', r3);
case 13:
_context.prev = 13;
_context.t0 = _context['catch'](0);
console.log(_context.t0);
case 16:
case 'end':
return _context.stop();
}
}
}, _callee, this, [[0, 13]]);
}));
return function r() {
return _ref.apply(this, arguments);
};
}();
function _asyncToGenerator(fn) {
return function () {
var gen = fn.apply(this, arguments);
return new Promise(function (resolve, reject) {
function step(key, arg) { // 相當於我們上門寫的next函式
try {
var info = gen[key](arg);
var value = info.value;
} catch (error) {
reject(error); return;
} if (info.done) {
resolve(value);
} else {
return Promise.resolve(value).then(function (value) { step("next", value); }, function (err) { step("throw", err); });
}
} return step("next");
});
};
}
複製程式碼
我們從callback promise generator async + await基本瞭解了非同步發展的程式了,
接下來我們用一個例子來貫穿這幾個~
我們有個需求,分別有3個球,一個球執行完動畫,才會輪到下一個球執行動畫,以此類推
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<style>
.ball {
position: absolute;
width: 100px;
height: 100px;
background: red;
border-radius: 50%;
left: 0;
}
div:nth-child(1) {
top: 0px
}
div:nth-child(2) {
top: 120px
}
div:nth-child(3) {
top: 240px
}
</style>
</head>
<body>
<div>
<div class="ball"></div>
<div class="ball"></div>
<div class="ball"></div>
</div>
<script>
// 4.------------------// async await
let balls = document.querySelectorAll('.ball');
function move(ele, distance) {
return new Promise((resolve, reject) => {
let movex = 0;
let interval = setInterval(() => {
movex++;
ele.style.left = movex + 'px';
if (movex >= distance) {
resolve();
clearInterval(interval);
}
}, 6)
})
}
async function m() {
await move(balls[0], 500);
await move(balls[1], 400);
await move(balls[2], 300)
}
m().then(data=>{
alert('ok');
});
// 3.------------------// generator + co
// let balls = document.querySelectorAll('.ball');
// function move(ele, distance) {
// return new Promise((resolve, reject) => {
// let movex = 0;
// let interval = setInterval(() => {
// movex++;
// ele.style.left = movex + 'px';
// if (movex >= distance) {
// resolve();
// clearInterval(interval);
// }
// }, 6)
// })
// }
// function* m() {
// yield move(balls[0], 500);
// yield move(balls[1], 400);
// yield move(balls[2], 300)
// }
// function co(it) {
// return new Promise((resolve, reject) => {
// function next(data) {
// let { value, done } = it.next(data);
// if (!done) {
// value.then(data => {
// next(data);
// }, reject);
// }else{
// resolve(value);
// }
// }
// next();
// })
// }
// co(m()).then(data => {
// alert('done')
// })
// 2.------------------// promise
// let balls = document.querySelectorAll('.ball');
// function move(ele, distance) {
// return new Promise((resolve, reject) => {
// let movex = 0;
// let interval = setInterval(() => {
// movex++;
// ele.style.left = movex + 'px';
// if (movex >= distance) {
// resolve();
// clearInterval(interval);
// }
// }, 6)
// })
// }
// move(balls[0],500).then(data=>{
// return move(balls[1],400)
// }).then(data=>{
// return move(balls[2],300)
// }).then(data=>{
// alert('ok');
// })
// 1.------------------// callback
// let balls = document.querySelectorAll('.ball');
// function move(ele, distance, cb) {
// let movex = 0;
// let interval = setInterval(()=>{
// movex++;
// ele.style.left = movex+'px';
// if(movex >= distance){
// cb();
// clearInterval(interval);
// }
// },6)
// }
// move(balls[0], 500, function () {
// move(balls[1], 400, function () {
// move(balls[2], 300, function () {
// alert('成功')
// })
// })
// })
</script>
</body>
</html>
複製程式碼