各种详细的Promise
教程已经满天飞了,我写这一篇也只是用来本身用来总结和整理用的。若是有不足之处,欢迎指教。javascript
JavaScript语言的一大特色就是单线程。单线程就意味着,全部任务须要排队,前一个任务结束,才会执行后一个任务。html
为了解决单线程的堵塞问题,如今,咱们的任务能够分为两种,一种是同步任务(synchronous),另外一种是异步任务(asynchronous)。java
异步任务必须指定回调函数,当主线程开始执行异步任务,就是执行对应的回调函数。而咱们可能会写出一个回调地狱。es6
step1(function (value1) {
step2(value1, function(value2) {
step3(value2, function(value3) {
step4(value3, function(value4) {
// ...
});
});
});
});
复制代码
而Promise 可让异步操做写起来,就像在写同步操做的流程,而没必要一层层地嵌套回调函数。web
(new Promise(step1))
.then(step2)
.then(step3)
.then(step4);
复制代码
关于Promise
的学术定义和规范能够参考Promise/A+规范,中文版【翻译】Promises/A+规范。json
Promise有三个状态pending
、fulfilled
、rejected
: 三种状态切换只能有两种途径,只能改变一次:promise
Promise 实例的then
方法,用来添加回调函数。bash
then
方法能够接受两个回调函数,第一个是异步操做成功时(变为fulfilled
状态)时的回调函数,第二个是异步操做失败(变为rejected
)时的回调函数(该参数能够省略)。一旦状态改变,就调用相应的回调函数。app
下面是一个写好注释的简单实现的Promise的实现:异步
const PENDING = 'pending'
const FULFILLED = 'fulfilled'
const REJECTED = 'rejected'
class AJPromise {
constructor(executor) {
this.state = PENDING
this.value = undefined
this.reason = undefined
this.onResolvedCallbacks = []
this.onRejectedCallbacks = []
let resolve = value => {
// 确保 onFulfilled 异步执行
setTimeout(() => {
if (this.state === PENDING) {
this.state = FULFILLED
this.value = value
// this.onResolvedCallbacks.forEach(fn => fn)
// 能够将 value 操做后依次传递
this.onResolvedCallbacks.map(cb => (this.value = cb(this.value)))
}
})
}
let reject = reason => {
setTimeout(() => {
if (this.state === PENDING) {
this.state = REJECTED
this.reason = reason
// this.onRejectedCallbacks.forEach(fn => fn)
this.onRejectedCallbacks.map(cb => (this.reason = cb(this.reason)))
}
})
}
try {
//执行Promise
executor(resolve, reject)
} catch (err) {
reject(err)
}
}
then(onFulfilled, onRejected) {
if (this.state === FULFILLED) {
onFulfilled(this.value)
}
if (this.state === REJECTED) {
onRejected(this.reason)
}
if (this.state === PENDING) {
typeof onFulfilled === 'function' &&
this.onResolvedCallbacks.push(onFulfilled)
typeof onRejected === 'function' &&
this.onRejectedCallbacks.push(onRejected)
// 返回 this 支持then方法能够被同一个 promise 调用屡次
return this
}
}
}
复制代码
若是须要实现链式调用和其它API,请查看下面参考文档连接中的手写Promise教程。
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
连接在一块儿来改变值,或依次运行额外的异步操做。
当从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!");
})
复制代码
如下是上述代码的流程图形式:
假设咱们获取了一个story.json
文件,其中包含了文章的标题,和段落的下载地址。
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';
})
复制代码
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';
})
复制代码
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
函数返回一个 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);
}
}
复制代码