异步的产生:
事件环,消息队列/事件(订阅、发布)参考 ① ②
异步的解决方式:
1.回调函数,可以通过回调函数将所有权转移到上层,实现了单一层次依赖的控制反转。
我们知道回调函数容易产生回调地狱,因为上层依赖树可能是多层的,并没有扁平化,下一个回调会依赖于前一个回调返回的结果。
2.Promise
你有没有不预约就进入一家繁忙餐厅的经历?在这种情况下,餐厅需要有一种方式在出现空桌时能够联系到你。过去,他们只会把你的名字记录下来并在出现空桌的时候呼喊你的名字。随后,他们自然而然地寻找更有意思的方案。有一种方式是他们不再记录你的名字,而是记录你的电话号码,当出现空桌的时候,他们就可以为你发送短信。这样一来,你就可以离开最初的呼喊范围了,但是更重要的是,这种方式允许他们在任何时候给你的电话发送广告。听起来很熟悉吧?应该是这样的!当然也可能并非如此。这种方式可以用来类比回调。将你的电话号码告诉餐厅就像将你的回调函数交给第三方服务一样。你期望餐厅在有空桌的时候给你发送短信,同样我们也期望第三方服务在合适的时候以它们承诺的方式调用我们函数。但是,一旦电话号码或回调函数交到了他们的手里,我们就完全失去对它的控制了。
幸好,还有另外一种解决方案。这种方案的设计允许你保留所有的控制权。你可能之前见过这种方式,那就是他们会给你一个蜂鸣器,如下所示。
如果你之前没有用过的话,它的想法其实非常简单。按照这种方式,他们不会记录你的名字或电话号码,而是给你一个这样的设备。当这个设备开始嗡嗡作响和发光时,就意味着有空桌了。在等待空桌的时候,你可以做任何你想做的事情,但此时你不需要放弃任何的东西。实际上,恰恰相反,是他们需要给你东西,这里没有所谓的控制反转。
蜂鸣器一定会处于如下三种状态之一:pending、fulfilled或rejected。
pending:默认状态,也是初始态。当他们给你蜂鸣器的时候,它就是这种状态。
fulfilled:代表蜂鸣器开始闪烁,你的桌子已经准备就绪。
rejected:如果蜂鸣器处于这种状态,则代表出现了问题。可能餐厅要打烊,或者他们忘记了晚上有人要包场。
再次强调,你作为蜂鸣器的接收者拥有完全的控制权。如果蜂鸣器处于fulfilled状态,你就可以就坐了。如果它进入fulfilled状态,但是你想忽略它,同样也可以。如果它进入了rejected状态,这非常糟糕,但是你可以选择去其他地方就餐。如果什么事情都没有发生,它会依然处于pending状态,你可能吃不上饭了,但是同时也没有失去什么。
Promise 的创建
1)构造函数的方式
const p = new Promise((resulve, reject) => {
setTimeout(() => {
resolve(1); // pending => resolved, fulfilled
reject(new Error("message")); // pending => rejected
}, 2000);
});
p
.then(result => console.log("Result", result))
.catch(err =>
console.log("Error", err.message)
);
2)静态方法
// Promise.resolve 与 Promise.reject
const p1 = Promise.reject(new Error(''));
p1.catch(err => console.log(err.message))
多重 Promise 结果依赖,避免 then 嵌套
getUser(1)
.then(user => getRepositories(user.gitHubUsername))
.then(repos => getCommits(repos[0]))
.then(commits => console.log('Commits', commits))
.catch(err => console.log('Error', err.message));
console.log('After');
function getUser(id) {
return new Promise((resolve, reject) => {
// Kick off some async work
setTimeout(() => {
console.log('Reading a user from a database...');
resolve({ id: id, gitHubUsername: 'mosh' });
}, 2000);
});
}
function getRepositories(username) {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log('Calling GitHub API...');
resolve(['repo1', 'repo2', 'repo3']);
}, 2000);
});
}
function getCommits(repo) {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log('Calling GitHub API...');
resolve(['commit']);
}, 2000);
});
}
Promise.all
接受一个 promise 的数组
等待所有这些 promise 完成
返回一个新的 Promise,将所有的 resolve 结果放进一个数组里
只要有一个 promise 失败/rejected,这个新的 promise 将会被 rejected
使用场景:避免 then 嵌套
如果Promise之间相互依赖,参考前面的用法。如果是回调依赖于两个 Promise的结果,则使用 Promise.all
//错误用法
.then(myVal => {
const promA = foo(myVal);
const promB = anotherPromMake(myVal);
return promA
.then(valA => {
return promB.then(valB => hungryFunc(valA, valB)); // very hungry!
})
})
//正确用法
.then(myVal => {
const promA = foo(myVal);
const promB = anotherPromMake(myVal);
return Promise.all([prom, anotherProm])
})
.then(([valA, valB]) => { // putting ES6 destructing to good use
console.log(valA, valB) // all the resolved values
return hungryFunc(valA, valB)
})
Promise.race
似于Promise.all() ,区别在于 它有任意一个返回成功后,就算完成,但是 进程不会立即停止
常见使用场景:把异步操作和定时器放到一起,如果定时器先触发,认为超时,告知用户。
let p1 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('成功了')
}, 2000);
})
let p2 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('success')
}, 5000);
})
Promise.race([p1, p2]).then((result) => {
console.log(result) //['成功了', 'success']
}).catch((error) => {
console.log(error)
})
3.async 与 await
async function displayCommits() {
try {
const user = await getUser(1);
const repos = await getRepositories(user.giconsole.log('Error', err.message)tHubUsername);
const commits = await getCommits(repos[0]);
console.log('Commits', commits);
} catch (err) {
console.log('Error', err.message)
}
}
displayCommits();
参考链接
①. React 组件通信
②. Nodejs 自定义事件