摘要:基礎從中的說起上和在異步操作中使用和是一件比較費勁的事情,而給我們提供了更為簡便的和。表達式會暫停當前的執行,等待處理完成。若正常處理,其回調的函數參數作為表達式的值,繼續執行。若處理異常,表達式會把的異常原因拋出。
寫在前面
本文首發于公眾號:【符合預期的CoyPan】
在上一篇文章中,梳理了javascript中的兩個重要概念:iterator和generator,并且介紹了兩者在異步操作中的應用。
【JS基礎】從JavaScript中的for...of說起(上) - iterator 和 generator
在異步操作中使用iterator和generator是一件比較費勁的事情,而ES2017給我們提供了更為簡便的async和await。
async和await asyncmdn上說:async function 聲明用于定義一個返回 AsyncFunction 對象的異步函數。異步函數是指通過事件循環異步執行的函數,它會通過一個隱式的 Promise 返回其結果。
簡單來說,如果你在一個函數前面使用了async關鍵字,那么這個函數就會返回一個promise。如果你返回的不是一個promise,JavaScript也會自動把這個值"包裝"成Promise的resolve值。例如:
// 返回一個promise async function aa() { return new Promise(resolve => { setTimeout(function(){ resolve("aaaaaa"); }, 1000); }); } aa().then(res => { console.log(res); // 1s后輸出 "aaaaaa" }); typeof aa === "function"; // true Object.prototype.toString(aa) === "[object AsyncFunction]"; // true Object.prototype.toString(aa()) === "[object Promise]"; // true // 返回一個非promise async function a() { return 1; } const b = a(); console.log(b); // Promise?{: 1} a().then(res => { console.log(res); // 1 })
當 async 函數拋出異常時,Promise 的 reject 方法也會傳遞這個異常值。例如下面的例子:
async function a(){ return bbb; } a() .then(res => { console.log(res); }) .catch( e => { console.log(e); // ReferenceError: bbb is not defined });await
await 操作符用于等待一個Promise 對象。它只能在異步函數 async function 中使用。await 表達式會暫停當前 async function 的執行,等待 Promise 處理完成。若 Promise 正常處理(fulfilled),其回調的resolve函數參數作為 await 表達式的值,繼續執行 async function。若 Promise 處理異常(rejected),await 表達式會把 Promise 的異常原因拋出。另外,如果 await 操作符后的表達式的值不是一個 Promise,則返回該值本身。看下面的例子:
const p = function() { return new Promise(resolve => { setTimeout(function(){ resolve(1); }, 1000); }); }; const fn = async function() { const res = await p(); console.log(res); const res2 = await 2; console.log(res2); }; fn(); // 1s后,會輸出1, 緊接著,會輸出2 // 把await放在try catch中捕獲錯誤 const p2 = function() { return new Promise(resolve => { console.log(ppp); resolve(); }); }; const fn2 = async function() { try { await p2(); } catch (e) { console.log(e); // ppp is not defined } }; fn2();
當代碼執行到await語句時,會暫停執行,直到await后面的promise正常處理。這和我們之前講到的generator一樣,可以讓代碼在某個地方中斷。只不過,在generator中,我們需要手動寫代碼去執行generator,而await則是像一個自帶執行器的generator。某種程度上,我們可以理解為:await就是generator的語法糖??聪旅娴拇a:
const p = function() { return new Promise(resolve, reject=>{ setTimeout(function(){ resolve(1); }, 1000); }); }; const f = async function() { const res = await p(); console.log(res); }
我們使用babel對這段代碼進行轉化,得到以下的代碼:
function _asyncToGenerator(fn) { return function () { var gen = fn.apply(this, arguments); return new Promise(function (resolve, reject) { function step(key, arg) { 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"); }); }; } var p = function p() { return new Promise(resolve, function (reject) { setTimeout(function () { resolve(1); }, 1000); }); }; var f = function () { var _ref = _asyncToGenerator( /*#__PURE__*/regeneratorRuntime.mark(function _callee() { var res; return regeneratorRuntime.wrap(function _callee$(_context) { while (1) { switch (_context.prev = _context.next) { case 0: _context.next = 2; return p(); case 2: res = _context.sent; console.log(res); case 4: case "end": return _context.stop(); } } }, _callee, this); })); return function f() { return _ref.apply(this, arguments); }; }();
通過變量名可以看到,babel也是將async await轉換成了generator來進行處理的。
任務隊列以下的場景其實是很常見的:
我們有一堆任務,我們需要按照一定的順序執行這一堆任務,拿到最終的結果。這里,把這一堆任務稱為一個任務隊列。
js中的隊列其實就是一個數組。
同步任務隊列任務隊列中的函數都是同步函數。這種情況比較簡單,我們可以采用reduce很方便的遍歷。
const fn1 = function(i) { return i + 1; }; const fn2 = function(i) { return i * 2; }; const fn3 = function(i) { return i * 100; }; const taskList = [fn1, fn2, fn3]; let a = 1; const res = taskList.reduce((sum, fn) => { sum = fn(sum); return sum; }, a); console.log(res); // 400異步任務隊列
任務隊列中的函數都是異步函數。這里,我們假設所有的函數都是以Promise的形式封裝的?,F在,需要依次執行隊列中的函數。假設異步任務隊列如下:
const fn1 = function() { return new Promise( resolve => { setTimeout(function(){ console.log("fn1"); resolve(); }, 2000); }); }; const fn2 = function() { return new Promise( resolve => { setTimeout(function(){ console.log("fn2"); resolve(); }, 1000); }); }; const fn3 = function() { console.log("fn3"); return Promise.resolve(1); }; const taskList = [fn1, fn2, fn3];
可以使用正常的for循環或者for...of... 來遍歷數組,并且使用async await來執行代碼(注:不要使用forEach,forEach不支持這種場景)
// for循環 (async function(){ for(let i = 0; i < taskList.length; i++) { await taskList[i](); } })(); // for..of.. (async function(){ for(let fn of taskList) { await fn(); } })();koa2洋蔥模型實現原理
koa2,大家都不陌生了。koa2的洋蔥模型,是怎么實現的呢?先來看下面的代碼:
const Koa = require("koa"); const app = new Koa(); // logger app.use(async (ctx, next) => { console.log(1); await next(); console.log(2); const rt = ctx.response.get("X-Response-Time"); console.log(`${ctx.method} ${ctx.url} - ${rt}`); }); // x-response-time app.use(async (ctx, next) => { console.log(3); const start = Date.now(); await next(); console.log(4); const ms = Date.now() - start; ctx.set("X-Response-Time", `${ms}ms`); }); // response app.use(async ctx => { console.log(5); ctx.body = "Hello World"; }); app.listen(3000); // 訪問node時,代碼輸出如下: // 1 // 3 // 5 // 4 // 2 // GET / - 6ms
其實實現起來很簡單,app.use就是將所有的回調函數都塞進了一個任務隊列里面,調用await next()的時候,會直接執行隊列里面下一個任務,直到下一個任務執行完成,才會接著執行后續的代碼。我們來簡單實現一下最基本的邏輯:
class TaskList { constructor(){ this.list = []; } use(fn) { fn && this.list.push(fn); } start() { const self = this; let idx = -1; const exec = function() { idx++; const fn = self.list[idx]; if(!fn) { return Promise.resolve(); } return Promise.resolve(fn(exec)) } exec(); } } const test1 = function() { return new Promise( resolve => { setTimeout(function(){ console.log("fn1"); resolve(); }, 2000); }); }; const taskList = new TaskList(); taskList.use(async next => { console.log(1); await next(); console.log(2); }); taskList.use(async next => { console.log(3); await test1(); await next(); console.log(4); }); taskList.use(async next => { console.log(5); await next(); console.log(6); }); taskList.use(async next => { console.log(7); }); taskList.start(); // 輸出: 1、3、fn1、5、7、6、4、2寫在后面
可以看到,使用async和await進行異步操作,可以使代碼看起來更為清晰,簡單。我們可以用同步代碼的方式來書寫異步代碼。本文還探究了前端開發中很常見的任務隊列的相關問題。通過本文和上一篇文章,我自己也對js中的異步操作有了更深入,更全面的認識。符合預期。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/103557.html
摘要:當這個迭代器的方法被首次后續調用時,其內的語句會執行到第一個后續出現的位置為止,后緊跟迭代器要返回的值。在這個回調函數里,我們使用第一個請求返回的,再次發起一個請求。 寫在前面 本文首發于公眾號:符合預期的CoyPan 后續文章:【JS基礎】從JavaScript中的for...of說起(下) - async和await 先來看一段很常見的代碼: const arr = [1, 2, ...
摘要:對于,除非使用箭頭函數,它的回調函數的將會變化。使用測試下面的代碼,結果如下打印打印要點使用的規則要求所有回調函數必須使用箭頭函數。 譯者按: JS 騷操作。 原文:For vs forEach() vs for/in vs for/of in JavaScript 譯者: Fundebug 本文采用意譯,版權歸原作者所有 我們有多種方法來遍歷 JavaScript 的數組或者...
摘要:從開始,就在引入新功能,來幫助更簡單的方法來處理異步編程,幫助我們遠離回調地獄。而則是為了更簡潔的使用而提出的語法,相比這種的實現方式,更為專注,生來就是為了處理異步編程。 從Promise開始,JavaScript就在引入新功能,來幫助更簡單的方法來處理異步編程,幫助我們遠離回調地獄。 Promise是下邊要講的Generator/yield與async/await的基礎,希望你已...
摘要:不幸的是,迭代器不能用來表示這樣的數據源。即使是的迭代器也是不夠的,因為它的是異步的,但是迭代器需要同步確定狀態。異步迭代器一個異步迭代器就像一個迭代器,除了它的方法返回一個的。 ES2018 新特性 異步迭代器(本文) 正則表達式反向(lookbehind)斷言 正則表達式 Unicode 轉義 非轉義序列的模板字符串 正則表達式 s/dotAll 模式 正則表達式命名捕獲組 對...
摘要:等待的基本語法該關鍵字的的意思就是讓編譯器等待并返回結果。這里并不會占用資源,因為引擎可以同時執行其他任務其他腳本或處理事件。接下來,我們寫一個火箭發射場景的小例子不是真的發射火箭 本文由云+社區發表 本篇文章,小編將和大家一起學習異步編程的未來——async/await,它會打破你對上篇文章Promise的認知,竟然異步代碼還能這么寫! 但是別太得意,你需要深入理解Promise后,...
閱讀 2078·2021-10-08 10:21
閱讀 2471·2021-09-29 09:34
閱讀 3494·2021-09-22 15:51
閱讀 4926·2021-09-22 15:46
閱讀 2314·2021-08-09 13:42
閱讀 3434·2019-08-30 15:52
閱讀 2723·2019-08-29 17:13
閱讀 1555·2019-08-29 11:30