共计 17376 个字符,预计需要花费 44 分钟才能阅读完成。
一、回调函数(callback)
1、概念
A callback is a function that is passed as an argument to another function and is executed after its parent function has completed.
翻译:回调函数是作为参数传递给另一个函数的函数,在父函数实现后执行。
2、例子阐明
var fs = require("fs");
var a
function f(x) {console.log(x)
}
function writeFile() {fs.writeFile('input01.txt', 'www.baidu.com', function (err) {if (!err) {console.log("文件写入结束!")
a = 1
}
});
}
a = 0
writeFile()
f(a)
输入后果:0
文件写入结束!
代码阐明 :设置一个全局变量 a = 0,后执行 writeFile 函数(也就是写入一个文件 input01.txt),这个函数外面有一行 c = 1,函数执行结束之后再跳进去调用 f() 函数,f()函数, 就是把打印一个变量。
依照“失常”逻辑,首先 a=0,而后调用 writeFile 函数,该函数外面有一句 a = 1,最初再调用 f(a),又因为调用 writeFile()是在 f(a)之前,所以 a=1 这条语句必定是会被执行到,那么后果应该是打印 1,后果居然是 0,明明咱们在 writeFile 函数里咱们从新对 a 进行了赋值,为什么后果还是 0 呢?
因为程序运行到 writeFile()这一行的时候,是一个比拟耗时的 IO 操作,JS 碰到这种操作并不会停在原地始终期待直到函数执行结束,而是间接运行下一条代码(即 f(a)),而此时 a = 1 这一行代码其实并没有被执行到,所以打印进去的后果还是 0 !
如果要打印出 a= 1, 只须要将 f(a)也放进 writeFile()函数外面,就能保障 a = 1 之后再调用 f(a):
var fs = require("fs");
var a
function f(x) {console.log(x)
}
function writeFile() {fs.writeFile('input01.txt', 'www.baidu.com', function (err) {if (!err) {console.log("文件写入结束!")
a = 1
f(a)
}
});
}
a = 0;
writeFile();
输入后果:文件写入结束!
1
代码阐明: 先执行 writeFile()函数,再执行 writeFile 中的 f(a),a = 1 肯定会被执行到。
然而改成这样并不完满,因为这么做就相当于将 f()” 焊死 ” 在 writeFile()里了,如果此处我最终想调用的函数不是 f()而是别的其余函数 f1(),f2(),f3()… 应该怎么写?难不成要写几个不同的 writeFile(),而他们之间的区别仅仅是最初调用的那个函数不同?显然是不可获得,而应用回调函数就可解决:“关键字”callback。(精确地说 callback 并不真的是 Javascript 里的关键字,然而鉴于大家都约定成俗把 callback 这个单词作为回调函数的默认抉择了,这里权且就不谨严地称它为 ” 关键字 ” 吧)
var fs = require("fs");
function f(x) {console.log(x)
}
function writeFile(callback) { //callback,示意这个参数不是一个一般变量,而是一个函数
fs.writeFile('input01.txt', 'www.baidu.com', function (err) {if (!err) {console.log("文件写入结束!")
a = 1
callback(a) // 因为咱们传进来的函数名是 f(),所以此行相当于调用一次 f(c)
}
});
}
var a = 0
writeFile(f) // 函数 f 作为一个参数传进 writeFile 函数
输入后果:文件写入结束!
1
代码阐明 :呈现了两次 callback, 第一个 callback呈现在 writeFile 的形参里,起定义的作用,示意这个参数并不是一个一般变量,而是一个函数,也就是后面所说的重点,即所谓的“以函数为参数”。第二个 callback呈现在 a = 1 上面,示意此处“执行”从形参传递进来的那个函数。
这样一来,writeFile()函数在执行结束之后到底调用哪个函数就变“活”了,如果咱们想 writeFile()函数执行完之后并不是像第二个例子那样只能调用 f(),而是还有别的函数比如说 x() y() z(),那么只须要写成 writeFile(x),writeFile(y)… 就行了。
总结: 在大多数编程语言中,函数的形参总是从外向内传递参数,但在 JS 中,如果形参碰到“关键字”callback 则齐全相同,它示意从外向外反向调用某个内部函数。
此处并不一定非要写为“callback”,你能够任意写成 abc, foo... 等等。callback 只是一种约定俗成的写法,它明确地通知代码阅读者:此处是一个回调函数。
有时候,咱们会看到一些函数的形参列表里间接嵌套一个函数的状况,其本质上依然是回调函数,因为没有了函数名,所以也称 匿名函数。
var fs = require("fs");
function writeFile(callback) {fs.writeFile('input01.txt', 'www.baidu.com', function (err) {if (!err) {console.log("文件写入结束!")
a = 1
callback(a)
}
});
}
var a = 0
writeFile(function (x) {console.log(x)
});
输入后果:文件写入结束!
1
writeFile()函数不变,只是在调用它的时候,间接将函数体嵌在参数列表里了,其作用跟上一个例子齐全一样。其实在本例中,fs.writeFile 函数前面也有一个匿名回调函数 function (err) {},这个函数示意当文件写入结束后,就回调它,如果在写入过程中呈现了谬误,则通过变量 err 携带进去。
阐明: 在 JS 里,并非所有操作都是异步的,比方 for 循环,无论这个 for 循环须要耗时多长,零碎也肯定会等它转完之后才会执行上面的语句。
二、Promise 介绍
1、回调天堂
首先咱们来看什么是回调天堂
在应用 JavaScript 时,为了实现某些逻辑常常会写出层层嵌套的回调函数,如果嵌套过多,会极大影响代码可读性和逻辑,这种状况也被成为回调天堂。
function pick(next){setTimeout(()=>{console.log('收到信息',new Date());
next();},500);
}
function groundMouth(next){setTimeout(()=>{console.log('货物装载',new Date());
next();},400);
}
function blow(next){setTimeout(()=>{console.log('打包出仓',new Date());
next();},300);
}
function PEbag(next){setTimeout(()=>{console.log('集成装车',new Date());
next();},200);
}
function pack(){setTimeout(()=>{console.log('发送运输',new Date());
},100);
}
pick(()=>{groundMouth(()=>{blow(()=>{PEbag(()=>{pack();
});
});
});
});
输入后果:收到信息 2022-03-20T14:24:15.363Z
货物装载 2022-03-20T14:24:15.771Z
打包出仓 2022-03-20T14:24:16.072Z
集成装车 2022-03-20T14:24:16.273Z
发送运输 2022-03-20T14:24:16.375Z
如果还有后续步骤,须要始终嵌套上来,这样代码的可读性就变差了。
如果应用 Promise 该怎么解决呢?
2、Promise 介绍
2.1 Promise 的状态
用 new Promise 实例化的 promise 对象有以下三个状态。
- “has-resolution” 即 Fulfilled resolve(胜利) 时。此时会调用 onFulfilled
- “has-rejection” 即 Rejected reject(失败) 时。此时会调用 onRejected
- “unresolved” 即Pending 既不是 resolve 也不是 reject 的状态。也就是 promise 对象刚被创立后的初始化状态等
promise 对象的状态,从 Pending 转换为 Fulfilled 或 Rejected 之后,这个 promise 对象的状态就不会再产生任何变动。也就是说,只有异步操作的后果能够决定以后是哪一种状态,其余任何操作都无奈扭转这种状态;一旦状态扭转,就不会再扭转。
Promise 与 Event 等不同,在
.then
后执行的函数能够必定地说只会被调用一次。Promise 中的函数是同步的,只有.then 后的函数才是异步的
2.2 结构器
创立一个 Promise 对象,能够应用 new
调用 Promise
的结构器来进行实例化。
var promise = new Promise(function(resolve, reject) { // 异步解决
// 解决完结后、调用 resolve 或 reject
});
2.3 实例办法
- Promise.prototype.then
对通过 new 生成的 promise 对象为了设置其值在 resolve (胜利)/ reject(失败)时调用的回调函数 能够应用 promise.then()
实例办法(也就是说作用是为 Promise 实例增加状态扭转时的回调函数。)。
promise.then(onFulfilled, onRejected)
then
办法的第一个参数是 Resolved 状态的回调函数,第二个参数(可选)是 Rejected 状态的回调函数。
- resolve(胜利)时 onFulfilled 会被调用
- reject(失败)时 onRejected 会被调用
onFulfilled、onRejected 两个都为可选参数。
then
办法返回的是一个新的 Promise 实例(留神,不是原来那个 Promise 实例)。因而能够采纳链式写法,即 then
办法前面再调用另一个 `then 办法。
getJSON("/post/1.json") // 返回一个 Promise 对象
.then(function(post) {return getJSON(post.commentURL); // 返回一个 Promise 对象
})
.then(function funcA(comments) {console.log("Resolved:", comments);
}, function funcB(err) {console.log("Rejected:", err);
});
下面的代码应用 then
办法,顺次指定了两个回调函数。第一个回调函数实现当前,会将返回后果作为参数,传入第二个回调函数。采纳链式的 then,能够指定一组依照秩序调用的回调函数。
- Promise.prototype.catch()
promise.then
胜利和失败时都能够应用。另外在只想对异样进行解决时能够采纳 Promise.then(undefined, onRejected)
这种形式,只指定 reject 时的回调函数即可。
Promise.prototype.catch
办法是 .then(null, rejection)
的别名,用于指定产生谬误时的回调函数, 等同于抛出谬误。
上文的代码能够革新成如下:
getJSON("/post/1.json") // 返回一个 Promise 对象
.then(function(post) {return getJSON(post.commentURL); // 返回一个 Promise 对象
})
.then(function (comments) {console.log("Resolved:", comments);
})
.catch(err) {console.log("Rejected:", err);
});
须要留神的是,如果 Promise 状态曾经变成 Resolved,再抛出谬误是有效的。
var promise = new Promise(function(resolve, reject) {resolve('success');
throw new Error('error');
});
promise
.then(function(value) {console.log(value)
})
.catch(function(error) {console.log(error)
});
输入后果:success
下面代码中,Promise 在 resolve 语句前面,再抛出谬误,不会被捕捉,等于没有抛出。
Promise 对象的谬误具备“冒泡”性质,会始终向后传递,直到被捕捉为止。也就是说,谬误总是会被下一个 catch 语句捕捉。
var catchError = new Promise(function(resolve, reject) {setTimeout(function(){resolve('success')
}, 1000)
})
catchError
.then(function(value){console.log('a')
})
.then(function(value){throw new Error('test');
console.log('b')
})
.then(function(value){console.log('c')
})
.catch(function(error){console.log(error)
})
输入后果:a
Error: test
at D:\upyun\test\promise\promise12.js:26:9
下面代码中,一共有四个 Promise 对象:一个由 ’catchError’ 产生,三个由 then 产生。它们之中的第二个 then 办法出了谬误,中断了上面的 then 办法,间接被最初一个 catch 捕捉。
倡议总是应用 catch 办法,而不应用 then 办法的第二个处理错误的参数。
跟传统的 try / catch
代码块不同的是,如果没有应用 catch 办法指定错误处理的回调函数,Promise 对象抛出的谬误不会传递到外层代码,即不会有任何反馈。
var foo = function() {return new Promise(function(resolve, reject) {
// 上面一行会报错,因为 a 没有申明
resolve(a + 2);
});
};
foo().then(function() {console.log('everything is great');
});
输入后果:ReferenceError: a is not defined
下面代码中,foo
函数产生的 Promise 对象会报错,然而因为没有指定 catch 办法,这个谬误不会被捕捉,也不会传递到外层代码,导致运行后没有任何输入。
留神,Chrome 浏览器不恪守这条规定,它会抛出谬误“ReferenceError: x is not defined”。
var promise = new Promise(function(resolve, reject) {resolve("success");
setTimeout(function() {throw new Error('error')
}, 0)
});
promise.then(function(value) {console.log(value)
});
输入后果:success
D:\upyun\test\promise\promise12.js:49
throw new Error('error')
^
Error: error
下面代码中,Promise 指定在下一轮“事件循环”再抛出谬误,后果因为没有指定应用try...catch 语句
,就冒泡到最外层,成了未捕捉的谬误。因为此时,Promise 的函数体曾经运行完结了,所以这个谬误是在 Promise 函数体外抛出的。
Node.js 有一个 unhandledRejection
事件,专门监听未捕捉的 reject 谬误。unhandledRejection
事件的监听函数有两个参数,第一个是谬误对象,第二个是报错的 Promise 实例,它能够用来理解产生谬误的环境信息。
process.on('unhandledRejection', function(err, p) {console.error(err.stack)
});
须要留神的是,catch 办法返回的还是一个 Promise 对象,因而前面还能够接着调用 then 办法。
var foo = function() {return new Promise(function(resolve, reject) {
// 上面一行会报错,因为 a 没有申明
resolve(a + 2);
});
};
foo()
.catch(function(error) {console.log('Error', error);
})
.then(function() {console.log('success');
});
输入后果:Error ReferenceError: a is not defined
success
下面代码运行完 catch 办法指定的回调函数,会接着运行前面那个 then 办法指定的回调函数。如果没有报错,则会跳过 catch 办法。
2.4 静态方法
Promise 这样的全局对象还领有一些静态方法。
包含 Promise.all()
还有 Promise.resolve()
等在内,次要都是一些对 Promise 进行操作的 辅助办法。
- Promise.all
Promise.all() 办法接管一个 promise 的 iterable 类型(注:Array,Map,Set 都属于 ES6 的 iterable 类型)的输出,并且只返回一个 Promise
实例,那个输出的所有 promise 的 resolve 回调的后果是一个数组。这个 Promise
的 resolve 回调执行是在所有输出的 promise 的 resolve 回调都完结,或者输出的 iterable 里没有 promise 了的时候。它的 reject 回调执行是,只有任何一个输出的 promise 的 reject 回调执行或者输出不非法的 promise 就会立刻抛出谬误,并且 reject 的是第一个抛出的错误信息。
const promise1 = Promise.resolve(3);
const promise2 = 42;
const promise3 = new Promise((resolve, reject) => {setTimeout(resolve, 100, 'foo');
});
Promise.all([promise1, promise2, promise3]).then((values) => {console.log(values);
});
输入后果:[3, 42, 'foo']
语法
Promise.all(iterable);
参数
- iterable
- 一个可迭代对象,如
Array
或String
。
返回值
- 如果传入的参数是一个空的可迭代对象,则返回一个 已实现(already resolved) 状态的
Promise
。 - 如果传入的参数不蕴含任何
promise
,则返回一个 异步实现(asynchronously resolved)Promise
。留神:Google Chrome 58 在这种状况下返回一个 已实现(already resolved) 状态的Promise
。 - 其它状况下返回一个 解决中(pending) 的
Promise
。这个返回的promise
之后会在所有的promise
都实现或有一个promise
失败时 异步 地变为实现或失败。返回值将会依照参数内的promise
顺序排列,而不是由调用promise
的实现程序决定。
阐明
此办法在汇合多个 promise
的返回后果时很有用。
实现(Fulfillment): 如果传入的可迭代对象为空,Promise.all
会同步地返回一个已实现(resolved)状态的 promise
。
如果所有传入的 promise
都变为实现状态,或者传入的可迭代对象内没有 promise
,Promise.all
返回的 promise
异步地变为实现。
在任何状况下,Promise.all
返回的 promise
的实现状态的后果都是一个数组,它蕴含所有的传入迭代参数对象的值(也包含非 promise
值)。
失败 / 回绝(Rejection): 如果传入的 promise
中有一个失败(rejected),Promise.all
异步地将失败的那个后果给失败状态的回调函数,而不论其它 promise
是否实现。
-Promise.resolve()
Promise.resolve(value)
办法返回一个以给定值解析后的 Promise
对象。如果这个值是一个 promise,那么将返回这个 promise;如果这个值是 thenable(即带有"then"
办法),返回的 promise 会“追随”这个 thenable 的对象,采纳它的最终状态;否则返回的 promise 将以此值实现。此函数将类 promise 对象的多层嵌套展平。
语法
Promise.resolve(value);
参数
value
将被 Promise
对象解析的参数,也能够是一个 Promise
对象,或者是一个 thenable。
返回值
返回一个带着给定值解析过的 Promise
对象,如果参数自身就是一个 Promise
对象,则间接返回这个 Promise
对象。
形容
静态方法 Promise.resolve
返回一个解析过的 Promise
对象。
1. 应用动态 `Promise.resolve` 办法
Promise.resolve("Success").then(function(value) {console.log(value);
}, function(value) {// 不会被调用});
输入后果:Success
2.resolve 一个数组
var p = Promise.resolve([1,2,3]);
p.then(function(v) {console.log(v[0]); // 1
});
2.5 还有须要留神,Promise 创立后回立即执行
var promise = new Promise(function(resolve, reject) {console.log('Promise');
resolve();});
promise.then(function() {console.log('Resolved.');
});
console.log('hello world');
输入后果:Promise
hello world
Resolved.
下面代码中,Promise 新建后立刻执行,所以首先输入的是“Promise”。而后,then 办法指定的回调函数,将在以后脚本所有同步工作执行完才会执行,所以“Resolved”最初输入。
2.6 Promise 也是有毛病的
- 无奈勾销 Promise,一旦新建它就会立刻执行,无奈中途勾销。
- 如果不设置回调函数,Promise 外部抛出的谬误,不会反馈到内部。
- 当处于 Pending 状态时,无奈得悉目前停顿到哪一个阶段(刚刚开始还是行将实现)。
2.7 创立 Promise 对象流程
new Promise(fn)
返回一个 Promise 对象-
在
fn
中指定异步等解决逻辑- 处理结果失常的话,调用 resolve(处理结果值)
- 处理结果谬误的话,调用 reject(Error 对象)
2.8 创立 XHR 的 promise 对象
首先,创立一个用 Promise 把 XHR 解决包装起来的名为 getURL
的函数。
function getURL(URL) {return new Promise(function (resolve, reject) {var req = new XMLHttpRequest();
req.open('GET', URL, true);
req.onload = function () {if (req.status === 200) {resolve(req.responseText);
} else {reject(new Error(req.statusText));
}
};
req.onerror = function () {reject(new Error(req.statusText));
};
req.send();});
}
// 运行示例
var URL = "http://httpbin.org/get";
getURL(URL).then(function onFulfilled(value){console.log(value);
}).catch(function onRejected(error){console.error(error);
});
getURL
只有在通过 XHR 获得后果状态为 200
时才会调用 resolve
, 而其余状况 (获得失败) 时则会调用 reject
办法。
resolve(req.responseText)
resolve
函数的作用是,将 Promise
对象的状态从“未实现”变为“胜利”(即从 Pending
变为Resolved
),在异步操作胜利时调用,并将异步操作后果,作为参数传递进来。
参数并没有特地的规定,基本上把要传给回调函数参数放进去就能够了。(then 办法能够接管到这个参数值)
reject(new Error(req.statusText))
reject
函数的作用是,将 Promise
对象的状态从“未实现”变为“失败”(即从 Pending
变为Rejected
),在异步操作失败时调用,并将异步操作报出的谬误,作为参数传递进来。
上文中,XHR 中 onerror
事件被触发的时候就是产生谬误时,所以天经地义调用 reject
。产生谬误时,创立一个 Error
对象后再将具体的值传进去。传给 的参数也没有什么非凡的限度,个别只有是 Error
对象 (或者 继承自Error 对象
) 就能够。
2.9 解决下面的回调天堂
function pick(){return new Promise((resolve, reject) => {setTimeout(()=>{console.log('收到信息',new Date());
resolve();},500);
})
}
function groundMouth(){return new Promise((resolve, reject) => {setTimeout(()=>{console.log('货物装载',new Date());
resolve();},400);
})
}
function blow(){return new Promise((resolve, reject) => {setTimeout(()=>{console.log('打包出仓',new Date());
resolve();},300);
})
}
function PEbag(){return new Promise((resolve, reject) => {setTimeout(()=>{console.log('集成装车',new Date());
resolve();},200);
})
}
function pack(){return new Promise((resolve, reject) => {setTimeout(()=>{console.log('发送运输',new Date());
resolve();},100);
})
}
pick()
.then(groundMouth)
.then(blow)
.then(PEbag)
.then(pack)
// pick(()=>{// groundMouth(()=>{// blow(()=>{// PEbag(()=>{// pack();
// });
// });
// });
// });
输入后果:收到信息 2022-03-21T03:24:56.520Z
货物装载 2022-03-21T03:24:56.925Z
打包出仓 2022-03-21T03:24:57.235Z
集成装车 2022-03-21T03:24:57.436Z
发送运输 2022-03-21T03:24:57.544Z
三、async await
首先阐明一下异步,以及常见的微工作和宏工作
1、异步,微工作,宏工作
异步
- 异步是用来解决 JS 单线程期待这种问题的
- 异步是基于回调函数的模式来实现的
- 常见的异步有:setTimeout、ajax、Promise……then、async/await、图片加载、网络申请资源
- 牢记 5 个版块 Call Stack、Web APIs、Browser console、Callback Queue、micro task queue 这五个版块走漏出异步的执行过程
-
宏工作是在 DOM 渲染后触发,微工作是在 DOM 渲染前触发
常见微工作和宏工作
- 常见的微工作:Promise……then、async/await
- 常见的宏工作:setTimeout、setInterval
2、async await 介绍
async/await
是 ECMAScript 2017 提出的内容。但事实上它们只是Promise
的语法糖。
因为 Promise 的呈现次要是为了解决异步的回调天堂问题。将噩梦般的嵌套回调变为了优雅的管道式回调。但这始终是逃不掉“回调”二字。而 async/await 虽说只是 Promise 的语法糖,但让你“脱离”了回调,拥抱了同步代码
1. 执行 async 函数,返回的是 Promise 对象
2.await 必须在 async 包裹之下执行
3.await 相当于 Promise 的 then 并且同一作用域下 await 上面的内容全副作为 then 中回调的内容
4.try……catch 可捕捉异样,代替了 Promise 的 catch
5. 异步中先执行微工作,再执行宏工作
async function fn() {return '我是 async 函数';}
console.log('async:', fn());
输入后果:async: Promise {'我是 async 函数'}
这里的 fn() 相当于 Promise.resolve(‘ 我是 async 函数 ’)
(async function() {const p = Promise.resolve('success');
const data = await p; // await 就相当于 Promise.then, 故 data 就是 then 的参数
console.log(data); // 这里的代码为 then 中回调的内容
})();
输入后果:success
下面的这段代码,如果把 async
删掉,后果肯定会报错!
(async function() {const p = Promise.reject('err');
// await + try...catch 相当于 Promise.catch
try {
const res = await p;
console.log(res);
} catch(ex) {// ex 来源于 reject()外面的数
console.error(ex);
}
})();
输入后果:err
下面的代码中:await + try…catch 相当于 Promise.catch
3、同步,异步,微工作,宏工作的执行程序
先同后异,先微后宏
- 例子如下:
async function async1 () {console.log('async1 start'); // 2
await async2();
console.log('async1 end'); // 6
}
async function async2 () {console.log('async2'); // 3
}
console.log('script start'); // 1
setTimeout(function () {console.log('setTimeout'); // 8
}, 0)
async1()
new Promise (function (resolve) {console.log('promise1') // 4
resolve()}).then(function () {console.log('promise2') // 7
})
console.log('script end') // 5
输入后果:script start
async1 start
async2
promise1
script end
async1 end
promise2
setTimeout
- 代码剖析:
1. 先执行同步代码。2. 所以首先执行 console.log('script start');
3. setTimeout 为宏工作,先不执行
4. 执行 async1 函数 console.log('async1 start'); 以及 async2(); await 因为是 Promise.then 的语法糖是异步代码,先不执行
5. new Promise() 外部代码要执行,前面的 then 的内容为微工作先不执行
6. 执行 console.log('script end')
7. 同步代码执行完结
8. 开始按代码程序执行微工作
9. 先执行 console.log('async1 end'); 后面说过,await 上面的代码相当于 then 里回调的内容
10.new Promise.then 外面的内容 console.log('promise2')
11. 最初执行 宏工作代码,即 setTimeout 里的内容。
四、promisify
1、Callback 与 Promise 间的桥梁 —— promisify
- Promisify
1.npm 装置
npm install bluebird
2. Node.js 后端引入(Common.js 标准)var Promise = require("bluebird");
3. 或者在 ES6 中
import * as Promise from "bluebird";
4. 如果那个 ES6 导入[不起作用](https://github.com/petkaantonov/bluebird/pull/1594)
import {Promise} from "bluebird";
Promise 很好地解决了异步办法的回调天堂、提供了咱们在异步办法中应用 return 的能力,并将 callback 的调用纳入了本人的治理,而不是交给异步函数后咱们就无能为力了(常常有 callback 被莫名调用两次而导致程序出错)。
2、promisify 介绍
就是“promise 化”,将一个不是 promise 的办法变成 promise。
- 作用
将本来须要通过传入回调参数来实现回调执行(或者叫同步执行)改为利用 promise
的.then
的形式来调用, 从而实现 逻辑上的同步操作。
const fs = require('fs');
const Promise = require('bluebird');
// 原有的 callback 调用
fs.readFile('input02.txt', function(err, data) {if (!err) {console.log('===:' + data.toString());
} else {console.log(err);
}
});
// promisify 后
var readFileAsync = Promise.promisify(fs.readFile);
readFileAsync('input01.txt').then(data => {console.log('---:' + data.toString());
}, err => {console.log(err);
});
输入后果:===: www.baidu.com
---: 我是通过 fs.writeFile 写入文件的内容
这两个办法成果上是等价的。后者掌控性更好!
那么什么样的办法能够通过 promisify 变成 promise 呢?这里就须要介绍一个名词,nodeCallback。什么样的 callback 叫 nodeCallback?
-
nodeCallback
有两个条件:1. 回调函数在主函数中的参数地位必须是最初一个;2. 回调函数参数中的第一个参数必须是 error。
回调函数在主函数中的参数地位
// 正确
function main(a, b, c, callback) { }
// 谬误
function main(callback, a, b, c) {}
回调函数参数中的第一个参数必须是 error
// 正确
function callback(error, result1, result2) { }
// 谬误
function callback(result1, result2, error) {}
通过 nodeCallback,咱们定义了一个能被 promisify 的函数的格局,即,满足 nodeCallback 模式的办法,咱们能够通过 promisify 来让它变成一个返回 promise 的办法。
3、promisify 应用
const Promise = require('bluebird');
async function main(a, b, callback) {const array1 = a.concat(b);
console.log('a=' + a);
console.log('b=' + b);
return callback(null, array1);
}
async function foo(c, d, callback) {const array2 = c.concat(d);
console.log('c=' + c);
console.log('d=' + d);
callback(null, array2);
}
async function fu(a,b) {// Promise.promisify 将不是 promise 的办法转为 promise 办法(实现同步),如果有 .then() 则为异步。const function1 = Promise.promisify(main);
const function2 = Promise.promisify(foo);
const data1 = await function1(a,b); // await 就相当于 Promise.then, 故 data 就是 then 的参数
console.log('data1:' + data1); // 这里的代码为 then 中回调的内容
for(let i = 0; i < data1.length; i++) {const data = data1[i];
const data2 = await function2(data1, b);
console.log('data2:' + data2);
}
}
const arr1 = [2,3,4,5];
const arr2 = [45,6,7,8];
fu(arr1,arr2);
输入:a=2,3,4,5
b=45,6,7,8
data1:2,3,4,5,45,6,7,8
c=2,3,4,5,45,6,7,8
d=45,6,7,8
data2:2,3,4,5,45,6,7,8,45,6,7,8
c=2,3,4,5,45,6,7,8
d=45,6,7,8
data2:2,3,4,5,45,6,7,8,45,6,7,8
c=2,3,4,5,45,6,7,8
d=45,6,7,8
data2:2,3,4,5,45,6,7,8,45,6,7,8
c=2,3,4,5,45,6,7,8
d=45,6,7,8
data2:2,3,4,5,45,6,7,8,45,6,7,8
c=2,3,4,5,45,6,7,8
d=45,6,7,8
data2:2,3,4,5,45,6,7,8,45,6,7,8
c=2,3,4,5,45,6,7,8
d=45,6,7,8
data2:2,3,4,5,45,6,7,8,45,6,7,8
c=2,3,4,5,45,6,7,8
d=45,6,7,8
data2:2,3,4,5,45,6,7,8,45,6,7,8
c=2,3,4,5,45,6,7,8
d=45,6,7,8
data2:2,3,4,5,45,6,7,8,45,6,7,8
代码阐明:fu()函数中,将 main()函数和 foo()函数进行了 promisify 化。将 asycn await 函数转换为 promise 函数。其中 callback 中,须要给第一个参数给为:null。如果不为 null 会有如下谬误:
const Promise = require('bluebird');
async function main(a, b, callback) {const array1 = a.concat(b);
console.log('a=' + a);
console.log('b=' + b);
return callback(array1);
}
async function foo(c, d, callback) {const array2 = c.concat(d);
console.log('c=' + c);
console.log('d=' + d);
callback(null, array2);
}
async function fu(a,b) {// Promise.promisify 将不是 promise 的办法转为 promise 办法(实现同步),如果有 .then() 则为异步。const function1 = Promise.promisify(main);
const function2 = Promise.promisify(foo);
const data1 = await function1(a,b); // await 就相当于 Promise.then, 故 data 就是 then 的参数
console.log('data1:' + data1); // 这里的代码为 then 中回调的内容
for(let i = 0; i < data1.length; i++) {const data = data1[i];
const data2 = await function2(data1, b);
console.log('data2:' + data2);
}
}
const arr1 = [2,3,4,5];
const arr2 = [45,6,7,8];
fu(arr1,arr2);
错误信息:
a=2,3,4,5
b=45,6,7,8
node:internal/process/promises:265
triggerUncaughtException(err, true /* fromPromise */);
^
[UnhandledPromiseRejection: This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled
with .catch(). The promise rejected with the reason "[object Array]".] {code: 'ERR_UNHANDLED_REJECTION'}