当我们谈论Promise时,我们说些什么
栏目: JavaScript · 发布时间: 7年前
内容简介:各类详细的JavaScript语言的一大特点就是单线程。单线程就意味着,所有任务需要排队,前一个任务结束,才会执行后一个任务。为了解决单线程的堵塞问题,现在,我们的任务可以分为两种,一种是同步任务(synchronous),另一种是异步任务(asynchronous)。
各类详细的 Promise 教程已经满天飞了,我写这一篇也只是用来自己用来总结和整理用的。如果有不足之处,欢迎指教。
为什么我们要用Promise
JavaScript语言的一大特点就是单线程。单线程就意味着,所有任务需要排队,前一个任务结束,才会执行后一个任务。
为了解决单线程的堵塞问题,现在,我们的任务可以分为两种,一种是同步任务(synchronous),另一种是异步任务(asynchronous)。
- 同步任务指的是,在主线程上排队执行的任务,只有前一个任务执行完毕,才能执行后一个任务;
- 异步任务指的是,不进入主线程、而进入"任务队列"(task queue)的任务,只有"任务队列"通知主线程,某个异步任务可以执行了,该任务才会进入主线程执行。
异步任务必须指定回调函数,当主线程开始执行异步任务,就是执行对应的回调函数。而我们可能会写出一个回调金字塔,维护大量的callback将是一场灾难:
step1(function (value1) {
step2(value1, function(value2) {
step3(value2, function(value3) {
step4(value3, function(value4) {
// ...
});
});
});
});
复制代码
而Promise 可以让异步操作写起来,就像在写同步操作的流程,而不必一层层地嵌套回调函数。
(new Promise(step1)) .then(step2) .then(step3) .then(step4); 复制代码
简单实现一个Promise
关于 Promise 的学术定义和规范可以参考Promise/A+规范,中文版 【翻译】Promises/A+规范 。
Promise有三个状态 pending 、 fulfilled 、 rejected : 三种状态切换只能有两种途径,只能改变一次:
- 异步操作从未完成(pending) => 成功(fulfilled)
- 异步操作从未完成(pending) => 失败(rejected)
Promise 实例的 then 方法,用来添加回调函数。
then 方法可以接受两个回调函数,第一个是异步操作成功时(变为 fulfilled 状态)时的回调函数,第二个是异步操作失败(变为 rejected )时的回调函数(该参数可以省略)。一旦状态改变,就调用相应的回调函数。
下面是一个写好注释的简单实现的Promise的实现:
class Promise {
constructor(executor) {
// 初始化state为pending
this.state = 'pending'
// 成功的值
this.value = undefined
// 失败的原因
this.reason = undefined
// 异步操作,我们需要将所有then中的成功调用保存起来
this.onResolvedCallbacks = []
// 异步操作,我们需要将所有then中的失败调用保存起来
this.onRejectedCallbacks = []
let resolve = value => {
// 检验state状态是否改变,如果改变了调用就会失败
if (this.state === 'pending') {
// resolve调用后,state转化为成功态
this.state = 'fulfilled'
// 储存成功的值
this.value = value
// 执行成功的回调函数
this.onResolvedCallbacks.forEach(fn => fn)
}
}
let reject = reason => {
// 检验state状态是否改变,如果改变了调用就会失败
if (this.state === 'pending') {
// reject调用后,state转化为失败态
this.state === 'rejected'
// 储存失败的原因
this.reason = reason
// 执行失败的回调函数
this.onRejectedCallbacks.forEach(fn => fn)
}
}
// 如果executor执行报错,直接执行reject
try {
executor(resolve, reject)
} catch (err) {
reject(err)
}
}
// then 方法 有两个参数onFulfilled onRejected
then(onFulfilled, onRejected) {
// 状态为fulfilled,执行onFulfilled,传入成功的值
if (this.state === 'fulfilled') {
onFulfilled(this.value)
}
// 状态为rejected,执行onRejected,传入失败的原因
if (this.state === 'rejected') {
onRejected(this.reason)
}
// 当状态state为pending时
if (this.state === 'pending') {
// onFulfilled传入到成功数组
this.onResolvedCallbacks.push(()=>{
onFulfilled(this.value);
})
// onRejected传入到失败数组
this.onRejectedCallbacks.push(()=>{
onRejected(this.reason);
})
}
}
}
复制代码
如果需要实现链式调用和其它API,请查看下面参考文档链接中的手写Promise教程。
优雅的使用Promise
使用Promise封装一个HTTP请求
function get(url) {
return new Promise(function(resolve, reject) {
var req = new XMLHttpRequest();
req.open('GET', url);
req.onload = function() {
if (req.status == 200) {
resolve(req.responseText);
}
else {
reject(Error(req.statusText));
}
};
req.onerror = function() {
reject(Error("Network Error"));
};
req.send();
});
}
复制代码
现在让我们来使用这一功能:
get('story.json').then(function(response) {
console.log("Success!", response);
}, function(error) {
console.error("Failed!", error);
})
// 当前收到的是纯文本,但我们需要的是JSON对象。我们将该方法修改一下
get('story.json').then(function(response) {
return JSON.parse(response);
}).then(function(response) {
console.log("Yey JSON!", response);
})
// 由于 JSON.parse() 采用单一参数并返回改变的值,因此我们可以将其简化为:
get('story.json').then(JSON.parse).then(function(response) {
console.log("Yey JSON!", response);
})
// 最后我们封装一个简单的getJSON方法
function getJSON(url) {
return get(url).then(JSON.parse);
}
复制代码
then() 不是Promise的最终部分,可以将各个 then 链接在一起来改变值,或依次运行额外的异步操作。
Promise.then()的异步操作队列
当从 then() 回调中返回某些内容时:如果返回一个值,则会以该值调用下一个 then() 。但是,如果返回类 promise 的内容,下一个 then() 则会等待,并仅在 promise 产生结果(成功/失败)时调用。
getJSON('story.json').then(function(story) {
return getJSON(story.chapterUrls[0]);
}).then(function(chapter1) {
console.log("Got chapter 1!", chapter1);
})
复制代码
错误处理
then() 包含两个参数 onFulfilled , onRejected 。 onRejected 是失败时调用的函数。
对于失败,我们还可以使用 catch ,对于错误进行捕捉,但下面两段代码是有差异的:
get('story.json').then(function(response) {
console.log("Success!", response);
}, function(error) {
console.log("Failed!", error);
})
get('story.json').then(function(response) {
console.log("Success!", response);
}).catch(function(error) {
console.log("Failed!", error);
})
// catch 等同于 then(undefined, func)
get('story.json').then(function(response) {
console.log("Success!", response);
}).then(undefined, function(error) {
console.log("Failed!", error);
})
复制代码
两者之间的差异虽然很微小,但非常有用。Promise 拒绝后,将跳至带有拒绝回调的下一个 then() (或具有相同功能的 catch() )。如果是 then(func1, func2) ,则 func1 或 func2 中的一个将被调用,而不会二者均被调用。但如果是 then(func1).catch(func2) ,则在 func1 拒绝时两者均被调用,因为它们在该链中是单独的步骤。看看下面的代码:
asyncThing1().then(function() {
return asyncThing2();
}).then(function() {
return asyncThing3();
}).catch(function(err) {
return asyncRecovery1();
}).then(function() {
return asyncThing4();
}, function(err) {
return asyncRecovery2();
}).catch(function(err) {
console.log("Don't worry about it");
}).then(function() {
console.log("All done!");
})
复制代码
以下是上述代码的流程图形式:
蓝线表示执行的 promise 路径,红路表示拒绝的 promise 路径。与 JavaScript 的 try/catch 一样,错误被捕获而后续代码继续执行。
并行和顺序:两者兼得
假设我们获取了一个 story.json 文件,其中包含了文章的标题,和段落的下载地址。
1. 顺序下载,依次处理
getJSON('story.json').then(function(story) {
addHtmlToPage(story.heading);
return story.chapterUrls.reduce(function(sequence, chapterUrl) {
// Once the last chapter's promise is done…
return sequence.then(function() {
// …fetch the next chapter
return getJSON(chapterUrl);
}).then(function(chapter) {
// and add it to the page
addHtmlToPage(chapter.html);
});
}, Promise.resolve());
}).then(function() {
// And we're all done!
addTextToPage("All done");
}).catch(function(err) {
// Catch any error that happened along the way
addTextToPage("Argh, broken: " + err.message);
}).then(function() {
// Always hide the spinner
document.querySelector('.spinner').style.display = 'none';
})
复制代码
2. 并行下载,完成后统一处理
getJSON('story.json').then(function(story) {
addHtmlToPage(story.heading);
// Take an array of promises and wait on them all
return Promise.all(
// Map our array of chapter urls to
// an array of chapter json promises
story.chapterUrls.map(getJSON)
);
}).then(function(chapters) {
// Now we have the chapters jsons in order! Loop through…
chapters.forEach(function(chapter) {
// …and add to the page
addHtmlToPage(chapter.html);
});
addTextToPage("All done");
}).catch(function(err) {
addTextToPage("Argh, broken: " + err.message);
}).then(function() {
document.querySelector('.spinner').style.display = 'none';
})
复制代码
3. 并行下载,一旦顺序正确立即渲染
getJSON('story.json').then(function(story) {
addHtmlToPage(story.heading);
// Map our array of chapter urls to
// an array of chapter json promises.
// This makes sure they all download parallel.
return story.chapterUrls.map(getJSON)
.reduce(function(sequence, chapterPromise) {
// Use reduce to chain the promises together,
// adding content to the page for each chapter
return sequence.then(function() {
// Wait for everything in the sequence so far,
// then wait for this chapter to arrive.
return chapterPromise;
}).then(function(chapter) {
addHtmlToPage(chapter.html);
});
}, Promise.resolve());
}).then(function() {
addTextToPage("All done");
}).catch(function(err) {
// catch any error that happened along the way
addTextToPage("Argh, broken: " + err.message);
}).then(function() {
document.querySelector('.spinner').style.display = 'none';
})
复制代码
async / await
async 函数返回一个 Promise 对象,可以使用 then 方法添加回调函数。当函数执行的时候,一旦遇到 await 就会先返回,等到异步操作完成,再接着执行函数体内后面的语句。
基本用法
我们可以重写一下之前的 getJSON 方法:
// promise 写法
function getJSON(url) {
return get(url).then(JSON.parse).catch(err => {
console.log('getJSON failed for', url, err);
throw err;
})
}
// async 写法
async function getJSON(url) {
try {
let response = await get(url)
return JSON.parse(response)
} catch (err) {
console.log('getJSON failed for', url, err);
}
}
复制代码
注意:避免太过循环
假定我们想获取一系列段落,并尽快按正确顺序将它们打印:
// promise 写法
function chapterInOrder(urls) {
return urls.map(getJSON)
.reduce(function(sequence, chapterPromise) {
return sequence.then(function() {
return chapterPromise;
}).then(function(chapter) {
console.log(chapter)
});
}, Promise.resolve())
}
复制代码
* 不推荐的方式:
async function chapterInOrder(urls) {
for (const url of urls) {
const chapterPromise = await getJSON(url);
console.log(chapterPromise);
}
}
复制代码
推荐写法:
async function chapterInOrder(urls) {
const chapters = urls.map(getJSON);
// log them in sequence
for (const chapter of chapters) {
console.log(await chapter);
}
}
复制代码
以上所述就是小编给大家介绍的《当我们谈论Promise时,我们说些什么》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Learning JavaScript
Shelley Powers / Oreilly & Associates Inc / 2006-10-17 / $29.99
As web browsers have become more capable and standards compliant, JavaScript has grown in prominence. JavaScript lets designers add sparkle and life to web pages, while more complex JavaScript has led......一起来看看 《Learning JavaScript》 这本书的介绍吧!