需求:
A、依次读取 A|B|C 三个文件,如果有失败,则立即终止。
B、同时读取 A|B|C 三个文件,如果有失败,则立即终止。
一、callback
需求A
:
let read = function (code) { if (code) { return true; } else { return false; } } let readFileA = function (callback) { if (read(1)) { return callback(null, "111"); } else { return callback("a fail"); } } let readFileB = function (callback) { if (read(1)) { return callback(null, "222"); } else { return callback("b fail"); } } let readFileC = function (callback) { if (read(1)) { return callback(null, "333"); } else { return callback("c fail"); } } readFileA(function (err, data) { if (err) { console.log("open file " + err); return; } console.log("读取 a.txt 成功!内容:" + data); readFileB(function (err, data) { if (err) { console.log("open file " + err); return; } console.log("读取 b.txt 成功!内容:" + data); readFileC(function (err, data) { if (err) { console.log("open file " + err); return; } console.log("读取 c.txt 成功!内容:" + data); }); }); });
return:
读取 a.txt 成功!内容:111 读取 b.txt 成功!内容:222 读取 c.txt 成功!内容:333
需求B
:太恶心了,不写了,总之很繁琐.
二、async.js
async.js
库的详细介绍可以见:[待写]
需求A
:
async.series
var async = require("async"); let read = function (code) { if (code) { return true; } else { return false; } } let readFileA = function (callback) { if (read(1)) { return callback(null, "111"); } else { return callback("a fail"); } } let readFileB = function (callback) { if (read(0)) { return callback(null, "222"); } else { return callback("b fail"); } } let readFileC = function (callback) { if (read(1)) { return callback(null, "333"); } else { return callback("c fail"); } } async.series([readFileA, readFileB, readFileC], function (err, datas) { if (err) { console.log("open file " + err); } console.log(datas); return; });
当第二个 readFileB() 读取失败的话:
return:
open file b fail [ '111', undefined ]
需求B
:
async.parallel
var async = require("async"); let read = function (code) { if (code) { return true; } else { return false; } } let readFileA = function (callback) { if (read(1)) { return callback(null, "111"); } else { return callback("a fail"); } } let readFileB = function (callback) { setTimeout(() => { if (read(0)) { return callback(null, "222"); } else { return callback("b fail"); } }, 1000); } let readFileC = function (callback) { if (read(1)) { return callback(null, "333"); } else { return callback("c fail"); } } async.parallel([readFileA, readFileB, readFileC], function (err, datas) { if (err) { console.log("open file " + err); } console.log(datas); return; });
当第二个 readFileB() 读取失败 (注意我给它加了 setTimeout,为了体现跟上面串行结果的不一样) 的话:
return:
open file b fail [ '111', undefined, '333' ]
总结:async.js 跟 callback 比的好处:
1、代码量少了,解决了回调地狱金字塔的缺陷
2、async 的第二个参数回调函数里,可以统一处理错误(建议用不同的 Error 类作区分)
3、成功返回的结果 datas 可以汇总到一个数组中方便处理
三、promise
[拓展]
promise 知识
new Promise()
// promise 在 new 的时候已经开始运行 new Promise(() => console.log("I have already started!"));
return:
I have already started!
promise.then(successCallback, failureCallback);
new Promise((resolve, reject) => resolve()).then(function (data) { console.log("success"); }, function (data) { console.log("fail"); })
return:
success
promise.catch(failureCallback)
// promise.catch(failureCallback) 是 promise.then(null, failureCallback) 的缩略形式 new Promise((resolve, reject) => reject()).catch( function (data) { console.log("fail"); })
return:
fail
链式调用
// 链式调用的原理:then 函数会返回一个新的 promise new Promise((resolve, reject) => reject()).then(function (data) { console.log("success_1"); }, function (err) { console.log("fail_1"); }).then(function (data) { console.log("success_2"); }, function (err) { console.log("fail_2"); });
return:
fail_1 success_2
提问
问1:
then 函数
会返回一个新的 promise,但是 then 的 successCallback 和 failureCallback 这两个回调函数里都没法调用 resolve() 和 reject(),那这个新的 promise 如何指定最终状态呢?
then 的 successCallback 和 failureCallback 里 | 等同于 |
---|---|
不返回 | resolve(undefined) |
return 1 | resolve(1) |
return Promise.resolve() | resolve() |
return Promise.reject() | reject() |
throw Error() | reject() |
return new Promise() | 以此类推 |
而普通的 promise 对象,如果不显示调用 resolve/reject ,则没有任何反应,例如:
new Promise((resolve, reject) => {return 1;}).then(function (data) { console.log("success"); }, function (err) { console.log("fail"); });
return:
没有任何输出
问2:then 函数如果
successCallback
和failureCallback
都为 null,会发生什么?
什么都不会发生,.then(null, null)
只要一方为 null,等于交给下一个 then 去接管这个回调
new Promise((resolve, reject) => reject()) .then(null, null) .then(null, null) .then(null, null) .then(null, null) .then(function (data) { console.log("success_2"); }, function (err) { console.log("fail_2"); });
所以按照上面 2 个提问揭示的规律,我们可以写成下面优雅的代码:
// 链式调用的原理:then 函数会返回一个新的 promise new Promise((resolve, reject) => resolve()).then((data) => { console.log("success_1"); }).then((data) => { console.log("success_2"); throw Error("error"); }).then((data) => { console.log("success_3"); }).catch((err) => { console.log(err); });
return:
success_1 success_2 Error: error ……
注:
.catch()
后还可以继续接.then()
或.catch()
这就达到了如下别人家同步代码的清晰的表达:
try { let result = syncDoSomething(); let newResult = syncDoSomethingElse(result); let finalResult = syncDoThirdThing(newResult); console.log(`Got the final result: ${finalResult}`); } catch(error) { console.log(error); }
所以,需求A
:
let read = function (code) { if (code) { return true; } else { return false; } } let readFileA = function () { return new Promise(function (resolve, reject) { if (read(1)) { resolve("111"); } else { reject("a fail"); } }); } let readFileB = function () { return new Promise(function (resolve, reject) { if (read(1)) { resolve("222"); } else { reject("b fail"); } }); } let readFileC = function () { return new Promise(function (resolve, reject) { if (read(1)) { resolve("333"); } else { reject("c fail"); } }); } //[串行] 场景:依次预加载多个资源,如果中途有失败,则进入 .catch() readFileA().then(function (data) { console.log("读取 a.txt 成功!内容:" + data); return readFileB(); }).then(function (data) { console.log("读取 b.txt 成功!内容:" + data); return readFileC(); }).then(function (data) { console.log("读取 c.txt 成功!内容:" + data); return "读取结束"; }).then(function (data) { console.log(data); return; }).catch(function (err) { console.log("open file " + err); })
promise
vs 事件监听
a. 事件监听
更多的是针对同一对象上发生多次的事情(如 keyup、touchstart 等)
而 promise
更多的表现这个对象最终走向什么状态,且不可改变。
但有个神奇的特型是一致的,事件监听
和 promise
都可以对同一事件的反应绑定多次的回调函数,如下面例子所示:
let promise = new Promise((resolve, reject) => { console.log("I have already started!"); resolve(); }) setTimeout(() => { promise.then(function (data) { console.log("success_1"); throw new Error(); }, function (err) { console.log("fail_1"); }); }, 2000); setTimeout(() => { promise.then(function (data) { console.log("success_2"); }, function (err) { console.log("fail_2"); }); }, 4000);
return:
I have already started! //又等待了2秒 success_1 (node:13150) UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 1): Error (node:13150) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code. //又等待了2秒 success_2
b. 跟 事件监听
不一样,如果 promise 已成功或失败了,过段时间再添加了回调函数,则还是可以成功调用回调。这个上面的例子也可以体现。
c. 事件监听
更多的关注某些功能的准确时间,promise
更多地是关注对结果作出的反应。
promise 扩展 API
Promise.resolve()
和 Promise.reject()
手动创建一个已经 resolve 或者 reject 的 promise 的快捷方法。
promise.all
:可以实现需求B
:
//promise.all [并行] 场景:预加载多个资源,都完成后才能进入页面 Promise.all([readFileA(), readFileB(), readFileC()]).then(function (datas) { console.log(datas); //所有promise都resolve,返回array return; }).catch(function (err) { console.log("open file " + err); //只要有一个promise是reject,返回这个reject的value })
promise.race
:
//promise.race [并行] 场景:taskA:fetch图片,taskB:settimeout抛错,让两个task赛跑实现请求超时报错功能 Promise.race([taskA(), taskB()]).then(function (data) { //进到resolve还是reject回调只取决于第一个确定状态的Promise console.log(data); return; }).catch(function (err) { console.log("读取图片超时"); })
总结:promise 跟 callback 比的好处:
1、代码量少了,解决了回调地狱金字塔的缺陷
2、.catch 可以统一处理错误(建议用不同的 Error 类作区分)
四、async / await
需求A
:
let read = function (code) { if (code) { return true; } else { return false; } } let readFileA = function () { return new Promise(function (resolve, reject) { if (read(1)) { resolve("111"); } else { reject("a fail"); } }); } let readFileB = function () { return new Promise(function (resolve, reject) { if (read(1)) { resolve("222"); } else { reject("b fail"); } }); } let readFileC = function () { return new Promise(function (resolve, reject) { if (read(1)) { resolve("333"); } else { reject("c fail"); } }); } async function test() { try { let re_a = await readFileA(); let re_b = await readFileB(); let re_c = await readFileC(); console.log({re_a, re_b, re_c}); //如果都成功,return: { re_a: '111', re_b: '222', re_c: '333' } } catch (err) { console.log(err); // 如果b失败,return: b fail } } test();
总结:async / await 跟 callback 比的好处:
1、代码量最少,解决了回调地狱金字塔的缺陷(Promise 通过 then 链来解决 callback 多层回调金字塔的问题,现在又用 async/await 来进一步优化它)(基于 promise 的 async / await 也试图淘汰 promise)
2、.catch 可以统一处理错误(建议用不同的 Error 类作区分)
[拓展]
1、async 函数就是 Generator 函数的语法糖,本质上并不是同步代码
2、async 用于申明一个 function 是异步的,而 await (async wait) 用于等待一个异步方法执行完成。
3、await 只能出现在 async 函数中,所以在代码的顶层,我们无法使用 await,所以添加它 .then/catch 来处理最终结果或掉落错误是正常的做法。
try { let re_a = await readFileA(); let re_b = await readFileB(); let re_c = await readFileC(); console.log({re_a, re_b, re_c}); } catch (err) { console.log(err); }
return:
报错
或者顶层使用立即执行函数表达式(IIFE)
(async () => { try { let re_a = await readFileA(); let re_b = await readFileB(); let re_c = await readFileC(); console.log({re_a, re_b, re_c}); } catch (err) { console.log(err); } })()
return:
{ re_a: '111', re_b: '222', re_c: '333' }
上面的例子还可以这样写:
async function test() { try { let re_a = await readFileA(); let re_b = await readFileB(); let re_c = await readFileC(); console.log({re_a, re_b, re_c}); //如果都成功,return: { re_a: '111', re_b: '222', re_c: '333' } } catch (err) { console.log(err); // 如果b失败,return: b fail } } test().then(function(data){ console.log("success"); },function(err){ console.log("fail"); });
return:
{ re_a: '111', re_b: '222', re_c: '333' } success
4、见上例,实际上 async 申明的 function 返回的就是一个 Promise 对象,这就是 await 必须用在 async 函数中的原因。async 函数调用不会造成阻塞,它内部所有的阻塞都被封装在一个 Promise 对象中异步执行。
区别是,async 申明的 function 里可以通过 return值 / 抛异常 来实现普通 Promise 的 resolve() / reject()
下面是对等关系:
// async 函数 async function foo () { return 'a' } // Promise function foo () { return Promise.resolve('a') }
// async 函数 async function foo () { throw new Error('error') } // Promise function foo () { return Promise.reject(new Error('error')) }
用 promise.all
实现需求B
async/await 同样适用于 Promise.all
,因为 Promise.all 本身返回的就是 promise 对象。
let read = function (code) { if (code) { return true; } else { return false; } } let readFileA = function () { return new Promise(function (resolve, reject) { if (read(1)) { resolve("111"); } else { reject("a fail"); } }); } let readFileB = function () { return new Promise(function (resolve, reject) { if (read(1)) { resolve("222"); } else { reject("b fail"); } }); } let readFileC = function () { return new Promise(function (resolve, reject) { if (read(1)) { resolve("333"); } else { reject("c fail"); } }); } async function test() { try { let re_a = await readFileA(); let re_b = await readFileB(); let re_c = await readFileC(); console.log({re_a, re_b, re_c}); } catch (err) { console.log(err); } } async function test() { try { let results = await Promise.all([ readFileA(), readFileB(), readFileC(), ]); console.log(results); } catch (err) { console.log(err); } } test();
参考资料
[使用 promises]
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Guide/Using_promises
[理解 JavaScript 的 async/await]
https://segmentfault.com/a/1190000007535316
[javascript.info-Async/await]
https://javascript.info/async-await#async-functions
来源:https://www.cnblogs.com/xjnotxj/p/9477987.html