异步编程综述
JavaScript 异步编程综述
浏览器或者 Node.js 这样的 JavaScript Runtime
const start = new Date();
setTimeout(function () {
const end = new Date();
console.log("Time elapsed:", end - start, "ms");
}, 500);
while (new Date() - start < 1000) {}
更多关于 Promise 编排/异常处理/性能优化/Promise 实现/Event Loop 等内容可以参阅异步并发章节中的其他内容。
Callback | 回调
回调常用于事件监听中,
function processFile(inputFile) {
const fs = require("fs"),
readline = require("readline"),
instream = fs.createReadStream(inputFile),
outstream = new (require("stream"))(),
rl = readline.createInterface(instream, outstream);
rl.on("line", function (line) {
console.log(line);
});
rl.on("close", function (line) {
console.log(line);
console.log("done reading file.");
});
}
processFile("/path/to/a/input/file.txt");
Callback Hell
Zalgo
Promise
一个 Promise 应该会处于以下的几个状态中:
- pending / Unfulfilled:初始状态,没有完成或者拒绝
- fulfilled / resolved:意味着该 Promise 已经完成
- rejected:意味着该操作失败
链式调用
链式调用
Generator
异步编程的核心目标是希望下一个异步操作能够等待上一个异步执行得到结果之后再运行,即实现函数的顺序执行。而生成器中其会在 yield
处停止执行直到触发下一个 next
调用,我们可以在 yield
后进行异步操作,然后在异步操作中执行生成器的 next
函数:
function asyncfuc(v) {
setTimeout(function () {
let r = v + 20;
console.log(r);
g.next(r); //把异步函数执行得到的结果传出并触发下一个yield
}, 500);
}
let g = (function* gen() {
let v1 = yield asyncfuc(0);
let v2 = yield asyncfuc(v1); //上一个异步调用的结果作为下一个异步调用的入参
return v2;
})();
g.next();
//这个方法用来模拟一个异步调用
function delay(time, callback) {
setTimeout(function () {
callback(`slept for ${time}`);
}, time);
}
function run(...functions) {
//构造一个生成器循环执行传入的方法
const generator = (function* sync(resume, functions) {
let result;
for (const func of functions) {
result = yield func(result, resume); //前一个方法执行的结果作为下一个方法的入参
}
return result;
})(resume, functions); //提供一个方法用于推进生成器执行。
function resume(callbackValue) {
generator.next(callbackValue);
}
generator.next(); //触发生成器立即执行第一个方法
} //模拟异步方法调用, 斐波那契数列
function d(result, resume) {
delay(1000, (msg) => {
let value = result;
if (value) {
[value.a, value.b] = [value.b, value.a + value.b];
} else {
value = { a: 0, b: 1 };
}
console.log(value.a);
resume(value);
});
return result;
}
run(d, d, d); //顺序执行异步方法
async/await
function asyncTask() {
return functionA()
.then((valueA) => functionB(valueA))
.then((valueB) => functionC(valueB))
.then((valueC) => functionD(valueC))
.catch((err) => logger.error(err));
}
const wait = (ms) => new Promise((r, j) => setTimeout(r, ms));
// Promise syntax
const prom = wait(2000); // prom, is a promise
const showdone = () => console.warn("done");
prom.then(showdone);
// same thing, using await syntax
await wait(2000);
console.warn("done");
ES6 Promise
ES6 中内置了 Promise 对象,不过目前 Babel 并没有对它进行转化 (ES5 中并没有相对应的方法 ),所以如果需要在老浏览中使用的话请参考es6-promise这个 Promise。如果对于 Promise 的执行顺序感到疑惑的可以参考下这个可视化的 Playground。
安装
npm install es6-promise
使用
const Promise = require('es6-promise').Promise;
如果需要自动的全局替换的话,利用:
require('es6-promise').polyfill();
即可。
不过对于 IE < 9 的情况,因为 catch 为保留的关键字,因此需要用如下方式:
promise["catch"](function (err) {
// ...
});
或者直接使用.then
来作为代替:
promise.then(undefined, function (err) {
// ...
});
Basic Usage
基本的 Promise 对象的声明为:
new Promise(executor);
new Promise(function(resolve, reject) { ... });
其中 executor 是一个包含了两个参数的方程。其中 resolve 表示履行或者实现了该 Promise,而第二个参数意味着拒绝了该 Promise。
const p1 = new Promise(function (resolve, reject) {
resolve("Success!");
// or
// reject ("Error!");
});
p1.then(
function (value) {
console.log(value); // Success!
},
function (reason) {
console.log(reason); // Error!
}
);
嵌套
一个完整的 Promise 的串联为:
最基本的串联方式是可以基于回调函数式的串联,即在一个 Promise 对象的 Resolve 或者 Reject 方法内调用另一个 Promise:
loadSomething().then(function (something) {
loadAnothering().then(function (another) {
DoSomethingOnThem(something, another);
});
});
鉴于 Promise 的 then 与 catch 方法本身也是返回一个 Promise 对象,因此它们可以很方便地即进行串联:
const p2 = new Promise(function (resolve, reject) {
resolve(1);
});
p2.then(function (value) {
console.log(value); // 1
return value + 1;
}).then(function (value) {
console.log(value); // 2
});
//注意,这里演示了如果对一个原始的Promise对象设置多个then方法,那么在不同的代码块里的then是不会自动串联的
p2.then(function (value) {
console.log(value); // 1
});
Promise.all: 等待多个 Promise 的结果
如果只是想对两个 promise 的结果做处理,可以使用 Promise.all 方法:
Promise.all([loadSomething, loadAnothering]).then(function(something, another) {
DoSomethingOnThem(something, another);
});
Promise.all 也可以用于对数组中的元素执行异步操作,例如需要对一个集合中的每个元素执行异步操作:
function workMyCollection(arr) {
const resultArr = [];
function _recursive(idx) {
if (idx >= resultArr.length) return resultArr;
return doSomethingAsync(arr[idx]).then(function (res) {
resultArr.push(res);
return _recursive(idx + 1);
});
}
return _recursive(0);
}
而如果引入了 Promise.all 的话,那就会清晰很多:
function workMyCollection(arr) {
return Promise.all(
arr.map(function(item) {
return doSomethingAsync(item);
})
);
}
断链
例如:
function anAsyncCall() {
const promise = doSomethingAsync();
promise.then(function () {
somethingComplicated();
});
return promise;
}
这里的问题在于加入 somethingComplicated()
出错的话不会被捕获。promise 应该链式调用。也就是说所有的 then
方法都应该返回一个新的 promise
。所以上面代码的正确写法为:
function anAsyncCall() {
const promise = doSomethingAsync();
return promise.then(function() {
somethingComplicated();
});
}
let isLoading = true;
fetch(myRequest)
.then(function (response) {
const contentType = response.headers.get("content-type");
if (contentType && contentType.includes("application/json")) {
return response.json();
}
throw new TypeError("Oops, we haven't got JSON!");
})
.then(function (json) {
/* process your JSON further */
})
.catch(function (error) {
console.log(error);
})
.finally(function () {
isLoading = false;
});
// 不使用 finally
showLoadingSpinner();
fetch("data.json")
.then((data) => {
renderContent(data);
hideLoadingSpinner();
})
.catch((error) => {
displayError(error);
hideLoadingSpinner();
});
// 使用 finally
showLoadingSpinner();
fetch("data.json")
.then((data) => {
renderContent(data);
})
.catch((error) => {
displayError(error);
})
.finally(() => {
hideLoadingSpinner();
});
async 与 await
async function test1() {
console.log("testpre1");
await sleep();
console.log("test1");
}
async function test2() {
console.log("test2");
}
(async () => {
await test1();
await test2();
const t1 = test1();
const t2 = test2();
await t1;
await t2;
})();
/*
输出结果
testpre1
test1
test2
testpre1
test2
test1
*/